Essential FastAPI Middleware Guide: Boost Security, Performance, and Functionality
This article explains how FastAPI middleware sits between incoming requests and outgoing responses, detailing built‑in and third‑party middleware such as CORS, GZip, HTTPS redirect, session, trusted host, error handling, rate limiting, authentication, custom headers, logging, timeout, trailing‑slash handling, IP whitelisting, proxy headers, CSRF protection, context management and global state, each with usage scenarios and code examples.
Middleware in a web framework acts as a processing layer between incoming requests and outgoing responses, allowing modification of requests, handling of exceptions, session management, response compression, and more. FastAPI supports any ASGI‑compatible middleware, enabling integration of built‑in, Starlette, or custom middleware.
1. CORS (Cross‑Origin Resource Sharing) Middleware
Description
CORS middleware allows or restricts resources on the server to be requested from other domains, which is essential when a FastAPI backend serves a frontend hosted on a different domain.
Use Cases
Enable secure cross‑origin requests in single‑page applications.
Allow API access from different client domains.
Example
<code>from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
app = FastAPI()
app.add_middleware(
CORSMiddleware,
allow_origins=["*"], # allow all origins
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
@app.get("/")
async def root():
return {"message": "Hello World"}
</code>2. GZip Middleware
Description
GZip middleware automatically compresses responses using the GZip algorithm, reducing payload size and improving performance, especially for large JSON or HTML responses.
Use Cases
Provide large JSON responses in RESTful APIs.
Compress static files.
Example
<code>from fastapi import FastAPI
from fastapi.middleware.gzip import GZipMiddleware
app = FastAPI()
app.add_middleware(GZipMiddleware, minimum_size=1000) # compress responses larger than 1000 bytes
@app.get("/")
async def root():
return {"message": "This is a test message that will be compressed."}
</code>3. HTTPS Redirect Middleware
Description
This middleware automatically redirects all HTTP requests to HTTPS, enforcing secure connections.
Use Cases
Force HTTPS for web applications.
Protect API endpoints by ensuring encrypted traffic.
Example
<code>from fastapi import FastAPI
from fastapi.middleware.httpsredirect import HTTPSRedirectMiddleware
app = FastAPI()
app.add_middleware(HTTPSRedirectMiddleware)
@app.get("/")
async def root():
return {"message": "You are being redirected to HTTPS!"}
</code>4. Session Middleware
Description
Session middleware creates a session ID for each user and stores it in a cookie, allowing stateful interactions across multiple requests.
Use Cases
Manage authentication sessions.
Store temporary data across requests.
Example
<code>from fastapi import FastAPI, Request
from starlette.middleware.sessions import SessionMiddleware
app = FastAPI()
app.add_middleware(SessionMiddleware, secret_key="your-secret-key")
@app.get("/set/")
async def set_session_data(request: Request):
request.session['user'] = 'john_doe'
return {"message": "Session data set"}
@app.get("/get/")
async def get_session_data(request: Request):
user = request.session.get('user', 'guest')
return {"user": user}
</code>5. Trusted Host Middleware
Description
Trusted host middleware filters requests to ensure they originate from specified hostnames, preventing HTTP host header attacks.
Use Cases
Prevent DNS rebinding attacks.
Restrict API access to specific hostnames.
Example
<code>from fastapi import FastAPI
from fastapi.middleware.trustedhost import TrustedHostMiddleware
app = FastAPI()
app.add_middleware(TrustedHostMiddleware, allowed_hosts=["example.com", "*.example.com"])
@app.get("/")
async def root():
return {"message": "This request came from a trusted host."}
</code>6. Error Handling Middleware
Description
This custom middleware catches exceptions and returns a unified error response, which can be extended for logging or monitoring.
Use Cases
Custom error logging.
Consistent error responses across the application.
Example
<code>from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
from starlette.middleware.base import BaseHTTPMiddleware
class ErrorHandlingMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request: Request, call_next):
try:
response = await call_next(request)
except Exception as e:
response = JSONResponse({"error": str(e)}, status_code=500)
return response
app = FastAPI()
app.add_middleware(ErrorHandlingMiddleware)
@app.get("/")
async def root():
raise ValueError("This is an error!")
</code>7. Rate Limit Middleware
Description
This middleware limits the number of requests a client can make within a given time window, helping prevent abuse and denial‑of‑service attacks.
Use Cases
Prevent API abuse by throttling request frequency.
Implement tiered usage for public APIs.
Example
<code>from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
from starlette.middleware.base import BaseHTTPMiddleware
import time
class RateLimitMiddleware(BaseHTTPMiddleware):
def __init__(self, app, max_requests: int, window: int):
super().__init__(app)
self.max_requests = max_requests
self.window = window
self.requests = {}
async def dispatch(self, request: Request, call_next):
client_ip = request.client.host
current_time = time.time()
if client_ip not in self.requests:
self.requests[client_ip] = []
self.requests[client_ip] = [ts for ts in self.requests[client_ip] if ts > current_time - self.window]
if len(self.requests[client_ip]) >= self.max_requests:
return JSONResponse(status_code=429, content={"error": "Too many requests"})
self.requests[client_ip].append(current_time)
return await call_next(request)
app = FastAPI()
app.add_middleware(RateLimitMiddleware, max_requests=5, window=60)
@app.get("/")
async def root():
return {"message": "You haven't hit the rate limit yet!"}
</code>8. Authentication Middleware
Description
This middleware validates tokens or credentials before a request reaches an endpoint, supporting JWT, OAuth, or custom schemes.
Use Cases
Validate JWT tokens for API requests.
Implement custom authentication logic.
Example
<code>from fastapi import FastAPI, Request, HTTPException
from starlette.middleware.base import BaseHTTPMiddleware
from fastapi.responses import PlainTextResponse
class AuthMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request: Request, call_next):
token = request.headers.get("Authorization")
if not token or token != "Bearer valid-token":
return PlainTextResponse(status_code=401, content="Unauthorized")
return await call_next(request)
app = FastAPI()
app.add_middleware(AuthMiddleware)
@app.get("/secure-data/")
async def secure_data():
return {"message": "This is secured data"}
</code>9. Custom Header Middleware
Description
Custom header middleware adds specific headers to every response, such as security headers, caching directives, or custom metadata.
Use Cases
Add authentication tokens or related IDs.
Enhance security with headers like X‑Content‑Type‑Options.
Control client caching behavior.
Example
<code>from fastapi import FastAPI
from starlette.middleware.base import BaseHTTPMiddleware
class CustomHeaderMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request, call_next):
response = await call_next(request)
response.headers['Cache-Control'] = 'public, max-age=3600'
response.headers['X-Content-Type-Options'] = 'nosniff'
response.headers['X-Frame-Options'] = 'DENY'
response.headers['X-XSS-Protection'] = '1; mode=block'
response.headers['Strict-Transport-Security'] = 'max-age=31536000; includeSubDomains'
return response
app = FastAPI()
app.add_middleware(CustomHeaderMiddleware)
@app.get("/data/")
async def get_data():
return {"message": "This response is cached for 1 hour."}
</code>10. Logging Middleware
Description
Logging middleware records each request and response, useful for debugging and monitoring API usage.
Use Cases
Debug issues by tracing request/response cycles.
Monitor API performance.
Example
<code>from fastapi import FastAPI, Request
import logging
from starlette.middleware.base import BaseHTTPMiddleware
logger = logging.getLogger("my_logger")
class LoggingMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request: Request, call_next):
logger.info(f"Request: {request.method} {request.url}")
response = await call_next(request)
logger.info(f"Response status: {response.status_code}")
return response
app = FastAPI()
app.add_middleware(LoggingMiddleware)
@app.get("/")
async def root():
return {"message": "Check your logs for the request and response details."}
</code>11. Timeout Middleware
Description
Timeout middleware aborts requests that exceed a specified duration, preventing long‑running requests from exhausting server resources.
Use Cases
Prevent resource exhaustion from lengthy requests.
Ensure timely responses in high‑traffic applications.
Example
<code>from fastapi import FastAPI, Request, HTTPException
from fastapi.responses import PlainTextResponse
import asyncio
from starlette.middleware.base import BaseHTTPMiddleware
class TimeoutMiddleware(BaseHTTPMiddleware):
def __init__(self, app, timeout: int):
super().__init__(app)
self.timeout = timeout
async def dispatch(self, request: Request, call_next):
try:
return await asyncio.wait_for(call_next(request), timeout=self.timeout)
except asyncio.TimeoutError:
return PlainTextResponse(status_code=504, content="Request timed out")
app = FastAPI()
app.add_middleware(TimeoutMiddleware, timeout=5)
@app.get("/")
async def root():
await asyncio.sleep(10) # simulate long processing
return {"message": "This won't be reached if the timeout is less than 10 seconds."}
</code>12. Trailing Slash Middleware
Description
This middleware normalizes URLs by adding or removing a trailing slash, ensuring consistent routing and avoiding duplicate routes.
Use Cases
Enforce a uniform URL structure across the app.
Prevent routing issues caused by inconsistent URLs.
Example
<code>from fastapi import FastAPI, Request
from starlette.middleware.base import BaseHTTPMiddleware
from fastapi.responses import RedirectResponse
class TrailingSlashMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request: Request, call_next):
if not request.url.path.endswith("/"):
return RedirectResponse(url=f"{request.url.path}/")
return await call_next(request)
app = FastAPI()
app.add_middleware(TrailingSlashMiddleware)
@app.get("/hello/")
async def hello():
return {"message": "Hello with trailing slash!"}
</code>13. IP Whitelist Middleware
Description
IP whitelist middleware restricts access based on the client’s IP address, allowing only requests from specified IPs.
Use Cases
Limit access to internal APIs.
Add an extra security layer by restricting IPs.
Example
<code>from fastapi import FastAPI, Request, HTTPException
from starlette.middleware.base import BaseHTTPMiddleware
from fastapi.responses import PlainTextResponse
class IPWhitelistMiddleware(BaseHTTPMiddleware):
def __init__(self, app, whitelist):
super().__init__(app)
self.whitelist = whitelist
async def dispatch(self, request: Request, call_next):
client_ip = request.client.host
if client_ip not in self.whitelist:
return PlainTextResponse(status_code=403, content="IP not allowed")
return await call_next(request)
app = FastAPI()
app.add_middleware(IPWhitelistMiddleware, whitelist=["127.0.0.1", "192.168.1.1"])
@app.get("/")
async def root():
return {"message": "Your IP is whitelisted!"}
</code>14. Proxy Headers Middleware
Description
When deploying behind a proxy (e.g., Nginx or an API gateway), this middleware reads proxy‑set headers like X‑Forwarded‑For and X‑Forwarded‑Proto to determine the original client IP and protocol.
Use Cases
Correctly identify client IP behind a proxy.
Handle HTTPS termination at the proxy layer.
Example
<code>from fastapi import FastAPI, Request
from uvicorn.middleware.proxy_headers import ProxyHeadersMiddleware
app = FastAPI()
app.add_middleware(ProxyHeadersMiddleware)
@app.get("/")
async def root(request: Request):
return {"client_ip": request.client.host}
</code>15. CSRF Middleware
Description
CSRFMiddleware adds Cross‑Site Request Forgery protection to FastAPI by generating and validating CSRF tokens, preventing unauthorized commands from being executed on behalf of authenticated users.
Use Cases
Protect forms and actions from CSRF attacks.
Ensure only authenticated users can perform legitimate operations.
Example
<code>pip install starlette-csrf</code> <code>from fastapi import FastAPI, Request
from starlette_csrf import CSRFMiddleware
app = FastAPI()
app.add_middleware(CSRFMiddleware, secret="__CHANGE_ME__")
@app.get("/")
async def root(request: Request):
return {"message": request.cookies.get('csrftoken')}
</code>16. Starlette Context Middleware
Description
starlette‑context is a third‑party middleware that maintains request‑scoped context, allowing storage and retrieval of data (e.g., headers, user info) without passing it through every function.
Use Cases
Attach request‑specific details to logs.
Store metadata needed across different parts of the app.
Track user information throughout the request lifecycle.
Example
<code>pip install starlette-context</code> <code>from fastapi import FastAPI, Depends
from fastapi.responses import JSONResponse
from starlette_context.middleware import ContextMiddleware
from starlette_context import context
app = FastAPI()
app.add_middleware(ContextMiddleware)
async def set_globals() -> None:
context["username"] = "chris"
@app.get("/", dependencies=[Depends(set_globals)])
async def info():
return JSONResponse(context.data)
</code>17. Globals Middleware
Description
GlobalsMiddleware provides global state management for ASGI applications, similar to Flask’s g object, enabling storage of configuration or request‑specific data accessible throughout the app.
Use Cases
Store global configuration accessible from any component.
Manage request‑specific data without explicit parameter passing.
Example
<code>pip install fastapi-g-context</code> <code>from fastapi import FastAPI, Depends
from fastapi_g_context import GlobalsMiddleware, g
app = FastAPI()
app.add_middleware(GlobalsMiddleware)
async def set_globals() -> None:
g.username = "JohnDoe"
g.request_id = "123456"
g.is_admin = True
@app.get("/", dependencies=[Depends(set_globals)])
async def info():
return {
"username": g.username,
"request_id": g.request_id,
"is_admin": g.is_admin,
}
</code>Code Mala Tang
Read source code together, write articles together, and enjoy spicy hot pot together.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.