Middleware
Middleware in FastAPI allows you to intercept and process every request before it reaches a path operation, and every response before it is sent to the client. This is ideal for cross-cutting concerns like logging, authentication, header manipulation, and resource management.
HTTP Middleware Decorator
The most straightforward way to add middleware is using the @app.middleware("http") decorator. This allows you to define a function that receives the Request and a call_next function.
When you use this decorator, FastAPI wraps your function in Starlette's BaseHTTPMiddleware.
import time
from fastapi import FastAPI, Request
app = FastAPI()
@app.middleware("http")
async def add_process_time_header(request: Request, call_next):
start_time = time.perf_counter()
# Process the request and get the response from the next handler
response = await call_next(request)
process_time = time.perf_counter() - start_time
# Modify the response
response.headers["X-Process-Time"] = str(process_time)
return response
Performance and Context Considerations
While BaseHTTPMiddleware is convenient, it has some known limitations in high-performance scenarios or when heavily relying on contextvars. Because it wraps the request/response cycle in a separate coroutine, it can introduce overhead and sometimes isolate context variable changes. For performance-critical middleware or complex context management, implementing a raw ASGI middleware class is often preferred.
Class-Based Middleware
For more complex logic or to use built-in middleware classes, you use app.add_middleware(). This method registers a middleware class into the application's middleware stack.
FastAPI provides several built-in middlewares re-exported from Starlette, available in the fastapi.middleware sub-packages:
CORSMiddleware: Handles Cross-Origin Resource Sharing.GZipMiddleware: Handles GZip compression for responses.TrustedHostMiddleware: Enforces that all incoming requests have a correctly setHostheader.HTTPSRedirectMiddleware: Redirects all incoming HTTP requests to HTTPS.
Example: CORS Middleware
CORS is a common requirement for web APIs. FastAPI makes it easy to configure via CORSMiddleware in fastapi/middleware/cors.py.
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
app = FastAPI()
origins = [
"http://localhost.tiangolo.com",
"https://localhost.tiangolo.com",
"http://localhost",
"http://localhost:8080",
]
app.add_middleware(
CORSMiddleware,
allow_origins=origins,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
The Middleware Stack and Execution Order
FastAPI builds a middleware "stack" that functions like an onion. When a request arrives, it passes through the outermost middleware first, then moves inward until it reaches the router. The response then travels back out through the same stack in reverse order.
Registration Order
In FastAPI, the last middleware added using app.add_middleware() becomes the outermost layer. This means it is the first to process the request and the last to process the response.
Internally, FastAPI.build_middleware_stack() in fastapi/applications.py constructs the final ASGI application by wrapping the router in several layers:
- ServerErrorMiddleware: The outermost layer, ensuring that even if a middleware fails, a 500 error is returned.
- User Middleware: The middlewares you added via
app.add_middleware()or@app.middleware("http"), in reverse order of addition. - ExceptionMiddleware: Handles exceptions raised by the router or dependencies.
- AsyncExitStackMiddleware: The innermost layer, just before the router.
Internal Resource Management
FastAPI includes a specialized AsyncExitStackMiddleware (defined in fastapi/middleware/asyncexitstack.py). Its primary purpose is to provide an AsyncExitStack in the request scope (under the key "fastapi_middleware_astack") to ensure resources like background files are properly closed after a request finishes.
class AsyncExitStackMiddleware:
def __init__(
self, app: ASGIApp, context_name: str = "fastapi_middleware_astack"
) -> None:
self.app = app
self.context_name = context_name
async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
async with AsyncExitStack() as stack:
scope[self.context_name] = stack
await self.app(scope, receive, send)
As noted in fastapi/applications.py, this middleware is placed inside all user-defined middlewares. This design choice ensures that the AsyncExitStack operates within the same contextvars context as the rest of the request processing, even if user middlewares create new task groups or context copies.
Custom ASGI Middleware
For maximum control, you can implement a raw ASGI middleware class. This requires implementing a __call__ method that accepts the ASGI scope, receive, and send parameters.
This approach is used in FastAPI's own tests (e.g., tests/test_custom_middleware_exception.py) to implement logic that needs to inspect or wrap the raw ASGI communication.
from fastapi import FastAPI, HTTPException
from starlette.types import ASGIApp, Receive, Scope, Send
class ContentSizeLimitMiddleware:
def __init__(self, app: ASGIApp, max_content_size: int):
self.app = app
self.max_content_size = max_content_size
async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
if scope["type"] != "http":
await self.app(scope, receive, send)
return
# Custom logic to wrap 'receive' and check content length
async def receive_wrapper():
message = await receive()
if message["type"] == "http.request":
body_len = len(message.get("body", b""))
if body_len > self.max_content_size:
# Note: In raw ASGI, you would send a response here
raise HTTPException(status_code=413, detail="Payload Too Large")
return message
await self.app(scope, receive_wrapper, send)
app = FastAPI()
app.add_middleware(ContentSizeLimitMiddleware, max_content_size=1024)
When implementing custom ASGI middleware, you are responsible for correctly handling the ASGI protocol and ensuring that you either call the next app in the stack or send a valid ASGI response.