← Back to FastAPI map
FastAPI · Architecture

Middleware

CORS so a browser app can call you, your own logging and timing wrappers, and the security ones that ship with FastAPI.

cross-cutting

Overview

Middleware wraps every request and response. Each layer sees the request on the way in, passes it on with call_next, and sees the response on the way out. That makes it the right place for concerns that apply to everything: CORS, compression, request ids, timing, logging, and trusted hosts. Middleware runs for every request, including ones that end in a 404, before routing and dependencies.

Key concepts

Onion model
The last middleware added is the outermost layer: it runs first on the way in and last on the way out.
CORS
Browsers block cross-origin requests unless the server says the origin is allowed. CORSMiddleware sends those headers and answers the preflight OPTIONS requests.
BaseHTTPMiddleware
The simple way to write middleware as dispatch(request, call_next). Pure ASGI middleware is faster for hot paths.
Middleware vs dependency
Middleware sees every request but only the raw request; dependencies apply only where declared and get typed, validated parameters.

Best practices

  • List explicit origins in CORS whenever credentials are involved.
  • Keep middleware fast - it runs on every request, health checks included.
  • Put authentication in dependencies rather than middleware, so each route shows its requirements and they appear in the OpenAPI docs.

Middleware

CORS middleware - frontend setup

CORS
from fastapi.middleware.cors import CORSMiddleware app.add_middleware( CORSMiddleware, allow_origins=["http://localhost:3000", "https://rte.dev"], allow_credentials=True, allow_methods=["GET", "POST", "PUT", "DELETE"], allow_headers=["*"], ) # For dev - allow all origins (NEVER in production) app.add_middleware(CORSMiddleware, allow_origins=["*"])

Watch out: allow_origins=["*"] cannot be combined with credentials: browsers refuse to send cookies or auth headers to a wildcard origin. List real origins whenever allow_credentials=True.

Custom middleware - logging, timing, request IDs

Custom
import time, uuid, logging from starlette.middleware.base import BaseHTTPMiddleware from fastapi import Request logger = logging.getLogger("api") class RequestLoggingMiddleware(BaseHTTPMiddleware): async def dispatch(self, request: Request, call_next): request_id = request.headers.get("X-Request-ID", uuid.uuid4().hex[:8]) start = time.perf_counter() try: response = await call_next(request) ms = round((time.perf_counter() - start) * 1000) logger.info(f"[{request_id}] {request.method} {request.url.path} -> {response.status_code} ({ms}ms)") response.headers["X-Request-ID"] = request_id response.headers["X-Response-Time"] = f"{ms}ms" return response except Exception as e: logger.error(f"[{request_id}] CRASH: {e}") raise app.add_middleware(RequestLoggingMiddleware) # Trusted host middleware (production security) from starlette.middleware.trustedhost import TrustedHostMiddleware app.add_middleware(TrustedHostMiddleware, allowed_hosts=["rte.dev", "*.rte.dev"]) # GZip compression from starlette.middleware.gzip import GZipMiddleware app.add_middleware(GZipMiddleware, minimum_size=1000)

Tip: Middleware added last runs first on the way in. Add CORS last so it wraps everything, including error responses from the other middleware.

Comments

Sign in to leave a comment. Your name and photo come from Google; nothing else is shared.

Loading comments...