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 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)