FastAPI Middleware — Logging, CORS and Custom Middleware
app.add_middleware() for global configurations like CORS or GZip compression. For custom logic, use the @app.middleware('http') decorator to wrap the call_next function. Key rule: Middleware execution follows an 'Onion' pattern—the last middleware added is the first to receive the request but the last to process the response.
CORS Middleware: Securing Cross-Origin Traffic
CORS is a security feature, not an error. When your frontend (e.g., React on port 3000) tries to talk to your FastAPI backend (port 8000), the browser blocks the request unless the server explicitly permits it. For production, never use ['*']. Always whitelist specific, trusted domains.
from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware app = FastAPI() # Configure the 'Onion' layers # CORSMiddleware should generally be added early in the stack app.add_middleware( CORSMiddleware, # List specific trusted origins for production allow_origins=[ 'https://api.thecodeforge.io', 'https://dashboard.thecodeforge.io', 'http://localhost:3000' ], # Required if your frontend sends cookies or Authorization headers allow_credentials=True, allow_methods=['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], allow_headers=['Authorization', 'X-Forge-Trace-ID', 'Content-Type'], ) # Note: If allow_credentials is True, allow_origins cannot be ['*']
Access-Control-Allow-Origin: https://dashboard.thecodeforge.io
Custom Middleware — Performance Logging
Custom middleware uses the call_next pattern. This allows you to run code before the request reaches your route and after the response has been generated. This is the ideal place to calculate 'Time to First Byte' (TTFB) or inject unique request identifiers for log aggregation.
from fastapi import FastAPI, Request import time import uuid import logging app = FastAPI() logger = logging.getLogger("thecodeforge.access") @app.middleware('http') async def add_process_time_header(request: Request, call_next): # 1. Logic BEFORE the route (Request Phase) start_time = time.perf_counter() request_id = str(uuid.uuid4()) # Inject trace ID into request state for downstream access request.state.trace_id = request_id # 2. Hand off to the next middleware or route handler response = await call_next(request) # 3. Logic AFTER the route (Response Phase) process_time = time.perf_counter() - start_time # Log the performance metric logger.info(f"RID: {request_id} | Path: {request.url.path} | Time: {process_time:.4f}s") # Standardize our response headers response.headers['X-Forge-Process-Time'] = str(process_time) response.headers['X-Forge-Trace-ID'] = request_id return response
🎯 Key Takeaways
- Middleware added last runs first for requests, and last for responses (LIFO order).
- Global Context: Middleware is protocol-agnostic regarding specific routes; it sees all traffic including 404s and health checks.
- The 'No Wildcard' Rule: You cannot use
allow_origins=['*']ifallow_credentialsis set toTruedue to W3C security specs. - State Sharing: Use
request.stateto pass variables (like user IDs or trace IDs) from middleware into your endpoint logic. - Avoid Blocking: Never perform heavy synchronous I/O inside a middleware's
async defwithout utilizing threads, as it will block the entire event loop for all users.
Interview Questions on This Topic
- QDescribe the execution flow of multiple middlewares in FastAPI. If Middleware A is added before Middleware B, which one sees the Response object first?
- QWhy is it a security risk to allow all origins ('*') in a production API that uses JWT cookies for authentication?
- QHow does the ASGI 'scope' differ from the FastAPI 'Request' object, and how would you access it inside a low-level middleware?
- QExplain the 'Short-Circuiting' behavior: How can a middleware return a response without ever calling the `call_next` function?
- QScenario: You need to implement IP-based rate limiting. Would you do this in a FastAPI middleware or a Dependency? Justify your choice based on performance and 'Route Matching' logic.
Frequently Asked Questions
What is the difference between middleware and a Depends() dependency?
Execution timing and scope are the key differences. Middleware executes at the 'Gateway' level before FastAPI even figures out which route should handle the request. This makes it perfect for logging and CORS. Dependencies (Depends()) execute after the route is matched but before the business logic runs. Use dependencies for logic that requires access to route parameters or endpoint-specific data.
Why am I getting CORS errors even after adding CORSMiddleware?
This usually happens due to one of three reasons: 1. You have allow_credentials=True but used a wildcard * for origins. 2. Your frontend is sending a custom header (like X-Requested-With) that isn't included in your allow_headers list. 3. Order of operations: If you have another middleware that returns a response (like an Auth check) before the CORSMiddleware is reached, the CORS headers won't be attached to the error response.
Is there a limit to how many middlewares I can add?
While there is no hard limit, each middleware layer adds a small amount of overhead to the request/response cycle. If you have 20+ middlewares, you may see an increase in latency. For complex transformations, consider moving logic into a background task or an external API gateway like Nginx or Kong.
Developer and founder of TheCodeForge. I built this site because I was tired of tutorials that explain what to type without explaining why it works. Every article here is written to make concepts actually click.