Middleware & CORS

intermediate fastapi middleware cors production

Middleware is code that wraps every request and response. Think of it like a security guard at a door — every visitor passes through, the guard can inspect, modify, or block them, then lets them through to the actual room. Logging, timing, auth headers, request IDs — all classic middleware jobs.

Custom middleware with @app.middleware("http")

The decorator gives us the simplest API. We receive the request, call the next handler with await call_next(request), get the response, and return it.

import time
import uuid
from fastapi import FastAPI, Request

app = FastAPI()

@app.middleware("http")
async def add_process_time_header(request: Request, call_next):
    start = time.perf_counter()
    request_id = str(uuid.uuid4())
    response = await call_next(request)
    duration = time.perf_counter() - start
    response.headers["X-Process-Time"] = f"{duration:.4f}"
    response.headers["X-Request-ID"] = request_id
    return response
Request lifecycle with middleware
Request ─► [middleware 1] ─► [middleware 2] ─► [route] ─►
                                                                                   │
Response ◄ [middleware 1] ◄ [middleware 2] ◄ [handler] ◄┘
Each middleware sees the request going in and the response coming out — onion layers.

Middlewares run in reverse order of registration on the way back out. Last added = outermost.

CORS — the browser security thing

Browsers block JavaScript from https://app.com calling https://api.com unless api.com says it’s OK. That’s the same-origin policy. CORS is how the server says “yes, this origin is allowed”.

Without CORS configured, our React app on localhost:3000 cannot call our API on localhost:8000. Browser blocks it before the request even gets a response payload.

from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware

app = FastAPI()

app.add_middleware(
    CORSMiddleware,
    allow_origins=["https://app.pman47.cc", "http://localhost:3000"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

Notes:

  • Never use allow_origins=["*"] with allow_credentials=True. Browsers reject that combo, and it’s a security footgun.
  • allow_methods=["*"] is fine for development. In prod, list them explicitly: ["GET", "POST", "PUT", "DELETE"].
  • CORS only matters for browsers. curl and server-to-server calls completely ignore CORS.

Other common built-in middlewares

from fastapi.middleware.gzip import GZipMiddleware
from fastapi.middleware.trustedhost import TrustedHostMiddleware
from fastapi.middleware.httpsredirect import HTTPSRedirectMiddleware

app.add_middleware(GZipMiddleware, minimum_size=1000)
app.add_middleware(TrustedHostMiddleware, allowed_hosts=["pman47.cc", "*.pman47.cc"])
app.add_middleware(HTTPSRedirectMiddleware)

Middleware vs Dependencies — when to use which

This trips a lot of people up.

MiddlewareDependency
ScopeEvery request, no exceptionsOnly routes that declare it
Access to path paramsNo (path isn’t matched yet)Yes
Can return earlyYes (return a Response directly)Yes (raise HTTPException)
Best forLogging, timing, CORS, gzip, request IDAuth, DB sessions, permission checks

In simple language: middleware is for things every request needs (logging, CORS). Dependencies are for things specific routes need (this endpoint requires a logged-in user).

For auth specifically, dependencies are usually the better call — they show up in OpenAPI docs, work with the /docs Authorize button, and let us mix protected and public routes cleanly.

Order matters

app.add_middleware(CORSMiddleware, ...)         # added second → outer
app.add_middleware(GZipMiddleware, ...)         # added first → inner

CORS should usually be outermost so it handles preflight OPTIONS requests before anything else. If our auth middleware runs before CORS, browser preflights get a 401 and the real request never fires.

Interview cheat sheet

  • Middleware = code wrapping every request. Use @app.middleware("http") or app.add_middleware(...).
  • CORS = browser thing. Configure CORSMiddleware with explicit origins for prod.
  • Middleware vs dependencies: middleware for cross-cutting concerns, dependencies for per-route logic.
  • Order matters — last added = outermost. CORS should be outer.
  • Curl and server-side clients don’t care about CORS.