FastAPI — Dependencies & Middleware
Dependency Injection Basics
from fastapi import Depends, FastAPI
app = FastAPI()
async def common_params(skip: int = 0, limit: int = 100):
return {"skip": skip, "limit": limit}
@app.get("/items")
async def list_items(params: dict = Depends(common_params)):
return params
Depends() resolves the function, caches the result per request, and injects it. Sync dependencies run in a threadpool; async ones run in the event loop.
Yield Dependencies (Teardown)
from collections.abc import AsyncGenerator
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
engine = create_async_engine("postgresql+asyncpg://user:pass@localhost/db")
async_session = async_sessionmaker(engine, class_=AsyncSession, expire_on_commit=False)
async def get_db() -> AsyncGenerator[AsyncSession, None]:
async with async_session() as session:
yield session
Code after yield runs after the response is sent — use for closing connections, releasing locks, cleanup.
Dependency Chains
from fastapi import Depends, HTTPException, status
async def get_current_user(db=Depends(get_db)):
user = "alice"
if not user:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED)
return user
async def get_admin_user(user=Depends(get_current_user)):
if user != "admin":
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN)
return user
@app.get("/admin/dashboard")
async def admin_dashboard(admin=Depends(get_admin_user)):
return {"admin": admin}
Dependencies form a DAG — resolved top-down, cached per request.
Class-Based Dependencies
from fastapi import Depends, Query
class Pagination:
def __init__(
self,
page: int = Query(default=1, ge=1),
size: int = Query(default=20, ge=1, le=100),
):
self.offset = (page - 1) * size
self.limit = size
@app.get("/items")
async def list_items(pagination: Pagination = Depends()):
return {"offset": pagination.offset, "limit": pagination.limit}
When Depends() has no argument, FastAPI uses the type annotation as the dependency.
Dependency Scopes
from fastapi import APIRouter, Depends, FastAPI, Header, HTTPException
async def verify_api_key(x_api_key: str = Header()):
if x_api_key != "secret":
raise HTTPException(status_code=403, detail="Invalid API key")
router = APIRouter(prefix="/protected", dependencies=[Depends(verify_api_key)])
app = FastAPI(dependencies=[Depends(verify_api_key)])
| Scope | Where | Effect |
|---|---|---|
| Endpoint | @app.get(..., dependencies=[]) |
Single route |
| Router | APIRouter(dependencies=[]) |
All routes in router |
| App | FastAPI(dependencies=[]) |
All routes globally |
Lifespan Context Manager
from contextlib import asynccontextmanager
from fastapi import FastAPI
from sqlalchemy.ext.asyncio import create_async_engine
@asynccontextmanager
async def lifespan(app: FastAPI):
app.state.db_engine = create_async_engine("postgresql+asyncpg://...")
yield
await app.state.db_engine.dispose()
app = FastAPI(lifespan=lifespan)
Everything before yield runs at startup, everything after at shutdown. Use app.state to store shared resources.
Middleware
import time
from fastapi import FastAPI, Request
from fastapi.middleware.cors import CORSMiddleware
app = FastAPI()
app.add_middleware(
CORSMiddleware,
allow_origins=["https://frontend.example.com"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
@app.middleware("http")
async def add_timing_header(request: Request, call_next):
start = time.perf_counter()
response = await call_next(request)
response.headers["X-Process-Time"] = f"{time.perf_counter() - start:.4f}"
return response
| Middleware | Purpose |
|---|---|
CORSMiddleware |
Cross-origin resource sharing |
TrustedHostMiddleware |
Host header validation |
GZipMiddleware |
Response compression |
Custom @app.middleware("http") |
Logging, timing, auth checks |
Exception Handlers
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
app = FastAPI()
class AppError(Exception):
def __init__(self, code: str, message: str, status: int = 400):
self.code = code
self.message = message
self.status = status
@app.exception_handler(AppError)
async def app_error_handler(request: Request, exc: AppError):
return JSONResponse(
status_code=exc.status, content={"error": exc.code, "message": exc.message},
)