FastAPI Cheatsheet
Middleware and CORS
Use this FastAPI reference while you build software engineering projects, review code, or refresh the syntax you reach for most.
Adding Middleware
Middleware wraps every request/response. FastAPI uses Starlette's ASGI middleware stack.
from fastapi import FastAPI, Request import time app = FastAPI() @app.middleware("http") async def add_process_time_header(request: Request, call_next): start = time.perf_counter() response = await call_next(request) elapsed = time.perf_counter() - start response.headers["X-Process-Time"] = str(elapsed) return response
call_next(request)passes the request down the stack and returns the response. Middleware must beasync def.
Middleware Execution Order
Middleware is applied in reverse registration order — the last add_middleware() call wraps the outermost layer.
app.add_middleware(MiddlewareA) # outer (runs first on request, last on response) app.add_middleware(MiddlewareB) # inner # Request path: MiddlewareA → MiddlewareB → route handler # Response path: route handler → MiddlewareB → MiddlewareA
CORS — Cross-Origin Resource Sharing
from fastapi.middleware.cors import CORSMiddleware app.add_middleware( CORSMiddleware, allow_origins=["https://myapp.com", "https://staging.myapp.com"], allow_origin_regex=r"https://.*\.myapp\.com", # regex alternative allow_credentials=True, # allow cookies / Authorization header allow_methods=["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"], allow_headers=["*"], # or list specific headers expose_headers=["X-Custom-Header"], # headers browser JS can read max_age=600, # preflight cache seconds (default 600) )
CORS Quick Reference
| Parameter | Values | Notes |
|---|---|---|
allow_origins | ["*"] or list of URLs | ["*"] disallows credentials |
allow_origin_regex | regex string | alternative to list |
allow_credentials | bool | cannot use with allow_origins=["*"] |
allow_methods | list or ["*"] | include OPTIONS for preflight |
allow_headers | list or ["*"] | custom headers must be listed |
expose_headers | list | headers JS can access on response |
max_age | seconds | preflight response cache duration |
Development / Permissive CORS
app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_methods=["*"], allow_headers=["*"], ) # NOTE: allow_credentials CANNOT be True when allow_origins=["*"]
Trusted Host Middleware
from fastapi.middleware.trustedhost import TrustedHostMiddleware app.add_middleware( TrustedHostMiddleware, allowed_hosts=["example.com", "*.example.com", "localhost"], ) # Returns 400 if Host header doesn't match
HTTPS Redirect Middleware
from fastapi.middleware.httpsredirect import HTTPSRedirectMiddleware app.add_middleware(HTTPSRedirectMiddleware) # Redirects all HTTP → HTTPS (301)
GZip Middleware
from fastapi.middleware.gzip import GZipMiddleware app.add_middleware(GZipMiddleware, minimum_size=1000) # Compresses responses >= 1000 bytes when client sends Accept-Encoding: gzip
Session Middleware (Starlette)
# pip install itsdangerous from starlette.middleware.sessions import SessionMiddleware app.add_middleware(SessionMiddleware, secret_key="your-secret", max_age=3600) @app.get("/set") def set_session(request: Request): request.session["user"] = "alice" return {"ok": True} @app.get("/get") def get_session(request: Request): return {"user": request.session.get("user")}
Custom Class-Based Middleware
from starlette.middleware.base import BaseHTTPMiddleware from starlette.requests import Request from starlette.responses import Response class RateLimitMiddleware(BaseHTTPMiddleware): def __init__(self, app, max_requests: int = 100): super().__init__(app) self.max_requests = max_requests self._counts: dict[str, int] = {} async def dispatch(self, request: Request, call_next) -> Response: ip = request.client.host self._counts[ip] = self._counts.get(ip, 0) + 1 if self._counts[ip] > self.max_requests: return Response("Too many requests", status_code=429) return await call_next(request) app.add_middleware(RateLimitMiddleware, max_requests=50)
Pure ASGI Middleware
class LoggingMiddleware: def __init__(self, app): self.app = app async def __call__(self, scope, receive, send): if scope["type"] == "http": print(f"{scope['method']} {scope['path']}") await self.app(scope, receive, send) app.add_middleware(LoggingMiddleware)
Accessing Request State in Middleware
@app.middleware("http") async def attach_request_id(request: Request, call_next): import uuid request.state.request_id = str(uuid.uuid4()) response = await call_next(request) response.headers["X-Request-ID"] = request.state.request_id return response @app.get("/items") def items(request: Request): print(request.state.request_id) # available here return []
Short-Circuiting (returning early from middleware)
@app.middleware("http") async def block_bots(request: Request, call_next): ua = request.headers.get("user-agent", "") if "badbot" in ua.lower(): return Response("Forbidden", status_code=403) return await call_next(request)
Middleware vs Depends — When to Use Each
| Use case | Approach |
|---|---|
| Apply to ALL routes (logging, CORS, headers) | Middleware |
| Apply to specific routes or groups | Depends() |
| Needs access to route-level params/body | Depends() |
| Runs before routing (before path matched) | Middleware |
| Needs to modify response headers globally | Middleware |
| Auth / permission logic | Depends() (has 422 support) |