FastAPI Middleware
Middleware is code that runs on every single request before it reaches your route and on every response before it goes back to the client. It is a gate that all traffic must pass through. Common uses include logging, adding response headers, measuring request time, and blocking requests from unknown sources.
The Middleware Pipeline
Client sends request
│
▼
[ Middleware 1 ] ← runs first (before)
│
▼
[ Middleware 2 ] ← runs second (before)
│
▼
[ Your Route Function ]
│
▼
[ Middleware 2 ] ← runs on the way back (after)
│
▼
[ Middleware 1 ] ← runs last (after)
│
▼
Client receives response
Middleware wraps around your route like layers of an onion. Each layer can read or modify both the request going in and the response coming out.
Writing Your First Middleware
import time
from fastapi import FastAPI, Request
app = FastAPI()
@app.middleware("http")
async def measure_request_time(request: Request, call_next):
start = time.time()
response = await call_next(request) ← passes to next layer
duration = time.time() - start
response.headers["X-Process-Time"] = str(duration)
return response
Every response now includes a custom header: X-Process-Time: 0.00312 (seconds)
call_next(request) passes the request to the next middleware or to the route function. Code before this line runs on the way in. Code after it runs on the way out.
Logging All Requests
@app.middleware("http")
async def log_requests(request: Request, call_next):
print(f"→ {request.method} {request.url}")
response = await call_next(request)
print(f"← {response.status_code}")
return response
Terminal output for GET /users/1: → GET http://127.0.0.1:8000/users/1 ← 200
Using Starlette's Built-In Middleware
FastAPI is built on Starlette, which provides ready-made middleware for common needs:
CORS Middleware
from fastapi.middleware.cors import CORSMiddleware
app.add_middleware(
CORSMiddleware,
allow_origins=["https://myapp.com"],
allow_methods=["GET", "POST"],
allow_headers=["*"]
)
GZip Middleware
from fastapi.middleware.gzip import GZipMiddleware app.add_middleware(GZipMiddleware, minimum_size=1000)
GZip compresses responses larger than 1000 bytes automatically. Clients receive smaller payloads and load faster.
Trusted Host Middleware
from starlette.middleware.trustedhost import TrustedHostMiddleware
app.add_middleware(
TrustedHostMiddleware,
allowed_hosts=["myapp.com", "*.myapp.com"]
)
Requests arriving with an unknown Host header receive a 400 error immediately — before any route runs.
Middleware vs Dependencies — When to Use Which
Middleware: Dependencies: Runs for EVERY request Runs only for specific routes No access to route-level context Full access to route parameters Best for: logging, CORS, Best for: auth, DB sessions, headers, timing, compression validation, per-route logic
Multiple Middleware — Execution Order
app.add_middleware(MiddlewareA) app.add_middleware(MiddlewareB) app.add_middleware(MiddlewareC) Execution order (request): C → B → A → route Execution order (response): route → A → B → C
The last middleware added runs first on the request. Think of it like a stack — last in, first out.
Key Points
- Middleware runs on every request and every response.
- Use
@app.middleware("http")to write custom middleware. - Call
await call_next(request)to pass the request down the chain. - Use
app.add_middleware()for built-in Starlette middleware classes. - Middleware suits global concerns; dependencies suit route-specific logic.
