Custom Middleware & Request Processing
Part of: Advanced FastAPI Patterns
Learn how to build custom middleware in FastAPI to intercept and process requests and responses, adding headers, logging, and timing information.
What You'll Learn
- Understand the middleware execution lifecycle in FastAPI
- Create custom middleware using the @app.middleware decorator
- Add custom response headers for request timing
- Apply middleware patterns for logging and monitoring
Theory and Concepts
Custom Middleware & Request Processing
What You'll Learn
- Understand what middleware is and how it fits into the request/response lifecycle
- Build custom middleware using the @app.middleware("http") decorator
- Add timing headers to track request processing duration
- Apply common middleware patterns for logging and monitoring
Theory
Middleware is code that runs for every request before it reaches your endpoint handler, and for every response before it is sent back to the client. Think of middleware as a pipeline that wraps around your application logic.
The Middleware Lifecycle
When a request arrives at your FastAPI application, it flows through middleware in this order:
1. Incoming Request - The request enters the middleware stack
2. Pre-processing - Your middleware code runs before call_next()
3. Route Handling - The request is dispatched to the matching endpoint
4. Post-processing - Your middleware code runs after call_next() returns
5. Outgoing Response - The modified response is sent to the client
[Code Example]
Creating Middleware in FastAPI
FastAPI uses the @app.middleware("http") decorator to register middleware functions. Each middleware function receives two arguments:
- request - The incoming Request object
- call_next - A function that passes the request to the next middleware or endpoint
[Code Example]
Middleware Execution Order
When you register multiple middleware functions, they execute in reverse order of registration. The last middleware registered is the outermost layer:
[Code Example]
Timing Middleware
One of the most common middleware patterns is measuring request processing time:
[Code Example]
This pattern captures the time before and after the request is processed, then attaches the duration as a custom response header.
Common Middleware Patterns
| Pattern | Purpose |
|---------|---------|
| Timing | Measure and report request duration |
| Logging | Log request method, path, and status code |
| Authentication | Verify tokens or API keys globally |
| CORS | Handle cross-origin resource sharing |
| Error Handling | Catch exceptions and return structured errors |
| Request ID | Attach a unique identifier to each request |
Key Concepts
- @app.middleware("http") - Registers an HTTP middleware function
- call_next(request) - Forwards the request to the next handler in the chain
- Pre-processing - Code before call_next() runs for every incoming request
- Post-processing - Code after call_next() runs for every outgoing response
- Custom Headers - Use response.headers["X-Header-Name"] to add headers
- Request Object - Access request.method, request.url, request.headers, etc.
Best Practices
- Keep middleware lightweight to avoid adding latency to every request
- Use time.time() for general timing; use time.perf_counter() when precision matters
- Follow the X- prefix convention for custom proprietary headers
- Always return the response from your middleware; forgetting return response will break the chain
- Order your middleware carefully: authentication should run before business logic middleware
- Avoid heavy I/O operations in middleware since they affect every single request
- Use middleware for cross-cutting concerns that apply to all or most endpoints
- For endpoint-specific logic, prefer dependencies over middleware
Additional Resources
- FastAPI Middleware Documentation
- Starlette Middleware
Helpful Hint
Use @app.middleware('http') to create middleware. The call_next function passes the request to the next handler. You can modify the response after awaiting call_next.
