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.

Leave a Comment

Your email address will not be published. Required fields are marked *