How do you create custom middleware with @app.middleware("http")?
Custom middleware in FastAPI intercepts every request before it reaches an endpoint and every response before it is sent to the client.
How @app.middleware("http") works:
- You decorate an async function with
@app.middleware("http") - The function receives the
requestobject and acall_nextcallable - You can modify the request, run logic before the endpoint, call
call_next(request)to pass to the next middleware/endpoint, then modify the response before returning it - You must call
call_next(request)and return aResponse
Execution flow:
Request → Middleware (before) → Endpoint → Middleware (after) → Response
Key parameters:
request: The incomingRequestobject (from Starlette)call_next: An async callable that passes the request to the next handler and returns aResponse
Use cases:
- Logging request/response details
- Adding custom headers to all responses
- Timing request duration
- Authentication checks (though dependencies are usually preferred)
- Request ID injection for distributed tracing
@app.middleware("http")
async def my_middleware(request, call_next):
# Before endpoint
do_something_before()
# Pass to next handler
response = await call_next(request)
# After endpoint
do_something_after()
return response
from fastapi import FastAPI, Request
from fastapi.testclient import TestClient
app = FastAPI()
@app.middleware("http")
async def add_custom_header(request: Request, call_next):
"""Middleware that adds a header and logs request info."""
print(f"Before: {request.method} {request.url.path}")
# Call the actual endpoint
response = await call_next(request)
# Modify response after endpoint runs
response.headers["X-Process-ID"] = "middleware-demo"
print(f"After: status {response.status_code}")
return response
@app.get("/hello")
def hello():
return {"message": "Hello, World!"}
@app.get("/items/{item_id}")
def get_item(item_id: int):
return {"item_id": item_id}
# --- Test ---
client = TestClient(app)
resp = client.get("/hello")
print(resp.json())
print(f"Custom header: {resp.headers.get('X-Process-ID')}")
resp = client.get("/items/42")
print(resp.json())
print(f"Custom header: {resp.headers.get('X-Process-ID')}")
- •The middleware function must be async and accept (request, call_next)
- •call_next(request) passes the request to the next middleware or endpoint
- •Code before call_next runs before the endpoint, code after runs after
- •You must return the response from the middleware
- •The middleware function must be async even if your endpoints are sync -- call_next is always awaited
- •If you forget to return the response, the client gets no response at all
- •You cannot read the response body in middleware without consuming the stream -- use a workaround like iterating response.body_iterator