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 be async 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

ParameterValuesNotes
allow_origins["*"] or list of URLs["*"] disallows credentials
allow_origin_regexregex stringalternative to list
allow_credentialsboolcannot use with allow_origins=["*"]
allow_methodslist or ["*"]include OPTIONS for preflight
allow_headerslist or ["*"]custom headers must be listed
expose_headerslistheaders JS can access on response
max_agesecondspreflight 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 caseApproach
Apply to ALL routes (logging, CORS, headers)Middleware
Apply to specific routes or groupsDepends()
Needs access to route-level params/bodyDepends()
Runs before routing (before path matched)Middleware
Needs to modify response headers globallyMiddleware
Auth / permission logicDepends() (has 422 support)