FastAPI is a high-performance web framework that simplifies building APIs with Python. One of its most powerful features is middleware, which allows developers to modify requests and responses globally before they reach the route handlers.
Table of Contents
What is Middleware?
Middleware is a function that runs before and after every request in FastAPI. It can:
- Modify the request before it reaches the route
- Modify the response before sending it back to the client
- Handle logging, authentication, request validation, error handling, CORS, and more

FastAPI uses the @app.middleware(“http”) decorator to define middleware.
Common Use Cases for Middleware
- Logging Middleware
- CORS (Cross-Origin Resource Sharing) Middleware
- GZip Compression Middleware
How Middleware Works in FastAPI
Middleware wraps every request in FastAPI, working like a chain of functions.
The execution order follows these steps:
- Middleware runs before the request reaches the route.
- The request is passed to the next middleware (if any).
- The request finally reaches the actual route.
- The response goes back through the middlewares in reverse order.
- The final response is sent to the client.
Middleware Basic Example
Let’s start with a simple middleware that logs all incoming requests:
from fastapi import FastAPI, Request
app = FastAPI()
@app.middleware("http")
async def log_requests(request: Request, call_next):
print(f"Request: {request.method} {request.url.path}")
response = await call_next(request) # Call the actual route
print(f"Response: {response.status_code}")
return response
@app.get("/")
async def home():
return {"message": "Hello, FastAPI!"}
PythonExplanation:
- Before processing the request, the middleware logs the request method and path.
- It calls call_next(request), which forwards the request to the actual route.
- After the response is generated, it logs the response status code.
Adding Custom Headers in Middleware
Middleware can modify responses before they are sent back.
@app.middleware("http")
async def add_custom_header(request: Request, call_next):
response = await call_next(request)
response.headers["X-Custom-Header"] = "Hello from Middleware"
return response
PythonEvery response will now include:
X-Custom-Header: Hello from Middleware
PythonError Handling Middleware
Middleware is also a great place to catch and handle errors globally.
from fastapi.responses import JSONResponse
@app.middleware("http")
async def error_handling_middleware(request: Request, call_next):
try:
response = await call_next(request)
return response
except Exception as e:
print(f"Error: {e}")
return JSONResponse(
status_code=500,
content={"message": "Something went wrong!"},
)
PythonThis ensures that even if a route crashes, a friendly JSON response is returned instead of exposing internal errors.
CORS (Cross-Origin Resource Sharing) Middleware
FastAPI has built-in support for CORS via CORSMiddleware from fastapi.middleware.cors.
from fastapi.middleware.cors import CORSMiddleware
app.add_middleware(
CORSMiddleware,
allow_origins=["*"], # Allow all origins
allow_credentials=True,
allow_methods=["*"], # Allow all methods (GET, POST, etc.)
allow_headers=["*"], # Allow all headers
)
PythonGZip Compression Middleware
Compress responses automatically to improve performance.
from fastapi.middleware.gzip import GZipMiddleware
app.add_middleware(GZipMiddleware, minimum_size=1000) # Compress responses > 1000 bytes
PythonCustom Authentication Middleware
You can check authentication before passing the request to the route.
@app.middleware("http")
async def auth_middleware(request: Request, call_next):
if "Authorization" not in request.headers:
return JSONResponse(status_code=401, content={"error": "Unauthorized"})
response = await call_next(request)
return response
PythonMiddleware Execution Order
When multiple middleware functions exist, they execute like a stack
@app.middleware("http")
async def first_middleware(request: Request, call_next):
print("First middleware (Before request)")
response = await call_next(request)
print("First middleware (After request)")
return response
@app.middleware("http")
async def second_middleware(request: Request, call_next):
print("Second middleware (Before request)")
response = await call_next(request)
print("Second middleware (After request)")
return response
PythonOutput
First middleware (Before request)
Second middleware (Before request)
Second middleware (After request)
First middleware (After request)
PythonMiddleware runs in order before the request, and in reverse order after the request.
Middleware vs Dependencies
- Middleware → Affects all requests globally.
- Dependencies → Used per route or for specific functionalities.
When to Use Middleware
Good for:
- Logging requests/responses
- Authentication & authorization
- Performance monitoring
- Global request modifications (e.g., headers, compression)
Not ideal for:
- Request validation (use Depends())
- Business logic (handle it inside route functions)
Different operations for different routes
You can inspect the request.url.path inside the middleware and apply different logic based on the route.
@app.middleware("http")
async def custom_middleware(request: Request, call_next):
start_time = time.time()
# Different logic for different routes
if request.url.path == "/items":
print("Logging request for /items")
elif request.url.path == "/users":
print("Processing /users route differently")
else:
print("General processing for", request.url.path)
response = await call_next(request) # Call the actual route
process_time = time.time() - start_time
print(f"Request {request.url.path} took {process_time:.2f} seconds")
return response
PythonUsing Multiple Middlewares for Different Routes
FastAPI applies all middlewares globally, so if you want to apply different logic to specific routes, you can:
- Use a dependency instead of middleware.
- Use a custom APIRouter with middleware (explained in the next section).
Using Middleware on Specific Routes with APIRouter
Instead of applying middleware to all routes, you can apply middleware only to specific routers.
Example: Apply Middleware Only to /items Routes
from fastapi import FastAPI, Request, APIRouter
import time
app = FastAPI()
items_router = APIRouter()
@items_router.middleware("http")
async def items_middleware(request: Request, call_next):
start_time = time.time()
print(f"Middleware for /items: {request.url.path}")
response = await call_next(request)
process_time = time.time() - start_time
print(f"/items request took {process_time:.2f} seconds")
return response
@items_router.get("/")
async def get_items():
return {"items": ["item1", "item2"]}
app.include_router(items_router, prefix="/items")
@app.get("/users")
async def get_users():
return {"users": ["Alice", "Bob"]}
PythonMiddleware applies only to /items/* routes but not to /users.
- If you request /items, the middleware runs.
- If you request /users, the middleware does NOT run.
Using Dependencies Instead of Middleware for Route-Specific Operations
If you only need specific logic for certain routes, use FastAPI dependencies instead of middleware.
from fastapi import Depends, FastAPI
app = FastAPI()
async def log_items_request():
print("Logging only for /items routes")
@app.get("/items", dependencies=[Depends(log_items_request)])
async def get_items():
return {"items": ["item1", "item2"]}
@app.get("/users")
async def get_users():
return {"users": ["Alice", "Bob"]}
Python- Middleware runs for all routes → Use for global logic.
- Dependencies run only on specific routes → Use for route-specific logic.
Conclusion
Middleware is a powerful feature in FastAPI for global request and response modifications. It allows you to:
- Log requests
- Enforce security rules
- Modify headers or responses
- Compress data
- Handle CORS