This is a cheatsheet — copy-paste-ready snippets for the routing layer. For the long-form treatment, see Chapter 2 of the textbook .
Path operation decorators
from fastapi import FastAPI
app = FastAPI()
@app.get("/items/{id}")
async def read(id: int): ...
@app.post("/items")
async def create(): ...
@app.put("/items/{id}")
async def replace(id: int): ...
@app.patch("/items/{id}")
async def update(id: int): ...
@app.delete("/items/{id}")
async def remove(id: int): ...
@app.head("/items")
async def head(): ...
@app.options("/items")
async def options(): ...
@app.trace("/items")
async def trace(): ...
@app.api_route("/multi", methods=["GET", "POST"])
async def multi(): ...
Decorators return the original function. Test handlers as plain coroutines without the framework — handy for unit testing.
Path parameters
@app.get("/items/{id}")
async def get_item(id: int): ...
@app.get("/users/{user_id}/posts/{post_id}")
async def get_post(user_id: int, post_id: int): ...
# Path-converters
@app.get("/files/{p:path}")
async def file(p: str): ... # matches slashes too
# Enum constraint
from enum import Enum
class Color(str, Enum):
R = "red"; G = "green"; B = "blue"
@app.get("/c/{color}")
async def c(color: Color): ... # only red/green/blue
# Path() metadata
from fastapi import Path
@app.get("/items/{id}")
async def get(id: int = Path(..., ge=1, le=10**9)): ...
Query parameters
@app.get("/search")
async def s(q: str, limit: int = 20, offset: int = 0):
...
# Optional
async def s(q: str | None = None): ...
# With Query() metadata
from fastapi import Query
async def s(
q: str | None = Query(None, min_length=2, max_length=100, pattern=r"^[a-z ]+$"),
tags: list[str] = Query(default_factory=list, description="filter tags"),
page: int = Query(1, ge=1, le=10000),
): ...
# alias / deprecated
async def s(q: str | None = Query(None, alias="search-query", deprecated=True)): ...
Lists in query string: ?tags=a&tags=b&tags=c → tags=["a", "b", "c"].
Body
from pydantic import BaseModel
class UserIn(BaseModel):
email: str
name: str
@app.post("/users")
async def create(user: UserIn): ...
# Multiple bodies → wrapped: {"user": {...}, "importance": 5}
from fastapi import Body
@app.put("/users/{id}")
async def update(id: int, user: UserIn, importance: int = Body(...)): ...
# Embed single body to wrap it
async def update(user: UserIn = Body(..., embed=True)): ...
# Raw body
from fastapi import Request
@app.post("/raw")
async def raw(req: Request):
body = await req.body()
Headers, cookies
from fastapi import Header, Cookie
@app.get("/h")
async def h(user_agent: str | None = Header(None)): ...
# convert_underscores by default: user_agent -> User-Agent
async def h(x_token: str = Header(...)): ...
@app.get("/c")
async def c(session: str | None = Cookie(None)): ...
# Setting cookies
from fastapi import Response
@app.post("/login")
async def login(response: Response):
response.set_cookie("session", "abc", httponly=True, secure=True, samesite="lax")
return {"ok": True}
Forms and files
from fastapi import Form, File, UploadFile
@app.post("/login")
async def login(username: str = Form(...), password: str = Form(...)): ...
@app.post("/upload")
async def upload(file: UploadFile):
chunk = await file.read()
return {"size": len(chunk), "ct": file.content_type, "name": file.filename}
@app.post("/multi")
async def multi(files: list[UploadFile]): ...
UploadFile streams; prefer over bytes. Requires python-multipart.
Routers
# api/users.py
from fastapi import APIRouter
router = APIRouter(prefix="/users", tags=["users"])
@router.get("/")
async def list_(): ...
@router.get("/{id}")
async def get(id: int): ...
# main.py
from fastapi import FastAPI
from .api import users
app = FastAPI()
app.include_router(users.router)
include_router options
app.include_router(
users.router,
prefix="/api/v1",
tags=["v1", "users"],
dependencies=[Depends(verify_api_key)],
responses={404: {"description": "Not found"}},
deprecated=False,
include_in_schema=True,
)
Nested routers
api = APIRouter(prefix="/api/v1")
api.include_router(users.router)
api.include_router(posts.router)
app.include_router(api)
Mounts
from fastapi.staticfiles import StaticFiles
app.mount("/static", StaticFiles(directory="static"), name="static")
app.mount("/admin", admin_app) # any ASGI app
Path operation kwargs
@app.post(
"/users",
response_model=UserOut,
status_code=201,
tags=["users"],
summary="Create a user",
description="Long description...",
response_description="Created user",
responses={400: {"description": "Bad input"}, 409: {"description": "Conflict"}},
deprecated=False,
operation_id="create_user",
include_in_schema=True,
)
async def create(...): ...
URL generation
url = app.url_path_for("create_user", ) # by function name (or operation_id)
url = router.url_path_for("get", id=42)
Custom APIRoute (per-route hook)
from fastapi.routing import APIRoute
class TimedRoute(APIRoute):
def get_route_handler(self):
original = super().get_route_handler()
async def custom(request):
t = time.perf_counter()
try:
return await original(request)
finally:
log.info("dur_ms", v=(time.perf_counter() - t) * 1000)
return custom
router = APIRouter(route_class=TimedRoute)
Powerful escape hatch for cross-cutting concerns.
Middleware
@app.middleware("http")
async def rid(req, call_next):
rid = req.headers.get("x-request-id") or uuid.uuid4().hex
resp = await call_next(req)
resp.headers["x-request-id"] = rid
return resp
Exceptions
from fastapi import HTTPException
@app.get("/x/{id}")
async def x(id: int):
if id < 0:
raise HTTPException(404, "not found")
# Custom exception handler
class AppError(Exception): ...
@app.exception_handler(AppError)
async def handle(req, exc):
return JSONResponse({"err": "bad"}, status_code=400)
Read this next
If you want my FastAPI starter (this layout, fully wired), it’s at rajpoot.dev .
Building something AI-, backend-, or data-heavy and want a second pair of eyes? I do consulting and freelance work — see my projects and ways to reach me at rajpoot.dev .