FastAPI
FastAPI is a modern, high-performance web framework for building APIs with Python 3.7+ based on standard Python type hints. It automatically generates interactive API documentation, performs request validation via Pydantic models, and supports asynchronous request handling out of the box.
Basic App
Learn More
For more examples and detailed explanations, see the Real Python guide on basic app.
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
def read_root():
return {"message": "Hello World"}
@app.get("/health")
def health_check():
return {"status": "ok"}Run with uvicorn main:app --reload. The interactive docs are at /docs and the alternative docs at /redoc.
Route Decorators
Learn More
For more examples and detailed explanations, see the Real Python guide on route decorators.
FastAPI maps HTTP methods to Python functions using decorators.
@app.get("/items/{item_id}")
def read_item(item_id: int):
return {"item_id": item_id}
@app.post("/items")
def create_item():
return {"message": "created"}
@app.put("/items/{item_id}")
def update_item(item_id: int):
return {"item_id": item_id}
@app.delete("/items/{item_id}")
def delete_item(item_id: int):
return {"item_id": item_id}
@app.patch("/items/{item_id}")
def patch_item(item_id: int):
return {"item_id": item_id}Path Parameters
Learn More
For more examples and detailed explanations, see the Real Python guide on path parameters.
Path parameters are declared as function parameters in the path. Type hints automatically validate and convert values.
@app.get("/users/{user_id}")
def get_user(user_id: int):
return {"user_id": user_id}
@app.get("/files/{file_path:path}")
def get_file(file_path: str):
"""Catch-all path parameter."""
return {"path": file_path}
@app.get("/items/{item_id}")
def get_item(item_id: int, q: str | None = None):
"""Path + optional query parameter."""
return {"item_id": item_id, "q": q}Query Parameters
Learn More
For more examples and detailed explanations, see the Real Python guide on query parameters.
Any function parameter not declared in the path is automatically treated as a query parameter.
@app.get("/items")
def list_items(
skip: int = 0,
limit: int = 10,
category: str | None = None,
in_stock: bool = False,
):
return {
"skip": skip,
"limit": limit,
"category": category,
"in_stock": in_stock,
}Request Body with Pydantic Models
Learn More
For more examples and detailed explanations, see the Real Python guide on fastapi pydantic request body.
Define request schemas using Pydantic models. FastAPI validates the request body, provides editor autocompletion, and generates JSON Schema for the docs.
from pydantic import BaseModel, Field
from datetime import date
class Item(BaseModel):
name: str = Field(..., min_length=1, max_length=100)
price: float = Field(..., gt=0)
description: str | None = None
tags: list[str] = []
created_at: date | None = None
class Order(BaseModel):
items: list[Item]
user_id: int
@app.post("/items")
def create_item(item: Item):
return {"item": item, "price_with_tax": item.price * 1.1}
@app.post("/orders")
def create_order(order: Order):
return {"order_count": len(order.items)}Query and Path Validators
Learn More
For more examples and detailed explanations, see the Real Python guide on query and path validators.
Use Query and Path from FastAPI to add validation, metadata, and documentation to parameters.
from fastapi import Query, Path
@app.get("/items/{item_id}")
def read_item(
item_id: int = Path(..., title="The ID of the item", ge=1, le=1000),
q: str | None = Query(None, min_length=3, max_length=50),
page: int = Query(1, ge=1, description="Page number"),
size: int = Query(10, ge=1, le=100, alias="page-size"),
):
return {"item_id": item_id, "q": q, "page": page, "size": size}
@app.get("/search")
def search(
q: str = Query(..., min_length=2, regex="^[a-zA-Z0-9]+$"),
deprecated_param: str | None = Query(None, deprecated=True),
):
return {"query": q}Dependency Injection
Learn More
For more examples and detailed explanations, see the Real Python guide on fastapi dependency injection.
FastAPI's Depends allows you to extract common logic into reusable dependencies.
from fastapi import Depends, HTTPException, Header
# Simple dependency — a function
async def common_params(
skip: int = 0,
limit: int = 100,
):
return {"skip": skip, "limit": limit}
@app.get("/items")
def list_items(params: dict = Depends(common_params)):
return params
# Class-based dependency
class Pagination:
def __init__(self, skip: int = 0, limit: int = 100):
self.skip = skip
self.limit = limit
@app.get("/users")
def list_users(pagination: Pagination = Depends()):
return {"skip": pagination.skip, "limit": pagination.limit}
# Dependency with authentication
async def verify_token(authorization: str = Header(...)):
if not authorization.startswith("Bearer "):
raise HTTPException(status_code=401, detail="Invalid token")
return authorization[7:] # Return the token
@app.get("/protected")
def protected_route(token: str = Depends(verify_token)):
return {"token": token, "message": "Authenticated"}
# Dependency with yield (resource cleanup)
from contextlib import asynccontextmanager
async def get_db():
db = await connect_to_database()
try:
yield db
finally:
await db.close()
@app.get("/data")
def read_data(db=Depends(get_db)):
return db.query("SELECT * FROM data")WARNING
Dependencies that use yield (for cleanup) are only supported in async routes or when using an async-compatible test client.
Background Tasks
Learn More
For more examples and detailed explanations, see the Real Python guide on background tasks.
Offload work that doesn't need to block the response.
from fastapi import BackgroundTasks
def write_log(message: str):
with open("log.txt", "a") as f:
f.write(f"{message}\n")
def send_email(email: str, body: str):
# Simulated email send
print(f"Sending email to {email}")
@app.post("/users")
def create_user(
name: str,
email: str,
background_tasks: BackgroundTasks,
):
background_tasks.add_task(write_log, f"User created: {name}")
background_tasks.add_task(send_email, email, "Welcome!")
return {"message": "User created", "name": name}CORS Middleware
Learn More
For more examples and detailed explanations, see the Real Python guide on fastapi cors.
Enable CORS so your frontend can call the API from a different origin.
from fastapi.middleware.cors import CORSMiddleware
app = FastAPI()
app.add_middleware(
CORSMiddleware,
allow_origins=[
"http://localhost:3000",
"https://myfrontend.com",
],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# Or allow all origins (development only)
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_methods=["*"],
allow_headers=["*"],
)Async Route Handlers
Learn More
For more examples and detailed explanations, see the Real Python guide on async route handlers.
Use async def for routes that perform I/O operations (database queries, HTTP requests, file reads).
import httpx
@app.get("/async-example")
async def async_endpoint():
async with httpx.AsyncClient() as client:
resp = await client.get("https://httpbin.org/json")
return resp.json()
@app.get("/concurrent")
async def concurrent_fetch():
async with httpx.AsyncClient() as client:
urls = [
"https://httpbin.org/delay/1",
"https://httpbin.org/delay/2",
"https://httpbin.org/delay/3",
]
tasks = [client.get(url) for url in urls]
results = await asyncio.gather(*tasks)
return [r.json() for r in results]WARNING
Avoid mixing async def with blocking I/O calls (e.g., time.sleep, synchronous file reads). Use def for CPU-bound or simple sync logic, and async def for async I/O operations.
File Upload
Learn More
For more examples and detailed explanations, see the Real Python guide on file upload.
Handle file uploads with UploadFile and File.
from fastapi import File, UploadFile
@app.post("/upload")
async def upload_file(file: UploadFile = File(...)):
contents = await file.read()
return {
"filename": file.filename,
"content_type": file.content_type,
"size": len(contents),
}
@app.post("/upload/multiple")
async def upload_multiple(files: list[UploadFile] = File(...)):
return [
{
"filename": f.filename,
"content_type": f.content_type,
}
for f in files
]
@app.post("/upload/save")
async def upload_and_save(file: UploadFile = File(...)):
import aiofiles
async with aiofiles.open(f"uploads/{file.filename}", "wb") as f:
while chunk := await file.read(1024):
await f.write(chunk)
return {"filename": file.filename, "saved": True}Error Handling
Learn More
For more examples and detailed explanations, see the Real Python guide on error handling.
Raise HTTPException for expected errors or register custom exception handlers.
from fastapi import HTTPException
@app.get("/items/{item_id}")
def read_item(item_id: int):
if item_id <= 0:
raise HTTPException(
status_code=400,
detail="Item ID must be positive",
)
fake_db = {1: "foo", 2: "bar"}
if item_id not in fake_db:
raise HTTPException(
status_code=404,
detail="Item not found",
headers={"X-Error": "Missing item"},
)
return {"item": fake_db[item_id]}
@app.get("/divide/{a}/{b}")
def divide(a: float, b: float):
if b == 0:
raise HTTPException(status_code=400, detail="Division by zero")
return {"result": a / b}
# Custom exception class
class InsufficientFunds(Exception):
def __init__(self, balance: float, amount: float):
self.balance = balance
self.amount = amount
@app.exception_handler(InsufficientFunds)
def insufficient_funds_handler(request, exc: InsufficientFunds):
from fastapi.responses import JSONResponse
return JSONResponse(
status_code=402,
content={
"detail": "Insufficient funds",
"balance": exc.balance,
"required": exc.amount,
},
)Automatic OpenAPI Docs
Learn More
For more examples and detailed explanations, see the Real Python guide on fastapi openapi docs.
FastAPI generates OpenAPI (Swagger) documentation automatically at /docs and /redoc. Customize the metadata in the app constructor.
app = FastAPI(
title="My API",
description="A sample API with automatic docs",
version="1.0.0",
contact={
"name": "Developer",
"url": "https://example.com",
"email": "dev@example.com",
},
license_info={
"name": "MIT",
"url": "https://opensource.org/licenses/MIT",
},
openapi_tags=[
{"name": "items", "description": "Item operations"},
{"name": "users", "description": "User operations"},
],
)
@app.get("/items", tags=["items"])
def list_items():
return [{"id": 1, "name": "foo"}]
@app.post("/users", tags=["users"])
def create_user():
return {"message": "created"}