FastAPI Cheatsheet
Middleware and CORS
Use this FastAPI reference while you build software engineering projects, review code for technical interview prep, or polish examples for a software engineer resume.
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) |