15 Useful Middlewares for FastAPI That You Should Know About 🚀

These are the middlewares I reach for when shipping real-world FastAPI projects.

15 Useful Middlewares for FastAPI That You Should Know About 🚀
Photo by Robert Shunev on Unsplash

FastAPI is already fast — but these middlewares make it cleaner, safer, and production-ready.

15 Useful Middlewares for FastAPI That You Should Know About 🚀

FastAPI has rapidly become the go-to framework for building high-performance APIs in Python. With its async-first approach, auto-generated docs, and seamless Pydantic integration, it’s no surprise developers love it.

But what really takes a FastAPI project from good to great?

Middleware.

Middlewares allow you to process requests before they reach your routes and responses before they’re returned to the client. They’re perfect for tasks like authentication, logging, error handling, rate limiting, and much more.

In this article, I’ve curated 15 useful FastAPI middlewares — both built-in and community-powered — that can seriously level up your backend game.


1. BaseHTTPMiddleware (Built-in)

Use this when you want full control over what happens before and after every request in your FastAPI app.

BaseHTTPMiddleware is part of Starlette, the ASGI toolkit that FastAPI is built on top of.

from fastapi import FastAPI, Request 
from starlette.middleware.base import BaseHTTPMiddleware 
 
app = FastAPI() 
 
class CustomHeaderMiddleware(BaseHTTPMiddleware): 
    async def dispatch(self, request: Request, call_next): 
        # 🔹 Before request 
        print(f"Request URL: {request.url}") 
         
        # Call the next middleware or route 
        response = await call_next(request) 
         
        # 🔹 After response 
        response.headers["X-Custom-Header"] = "MyValue" 
        return response 
 
# Add middleware to app 
app.add_middleware(CustomHeaderMiddleware) 
 
@app.get("/") 
async def read_main(): 
    return {"message": "Hello World"}

Perfect for custom logic like header injection or user tracking.

2. CORS Middleware

CORS (Cross-Origin Resource Sharing) is a mechanism that allows your frontend JavaScript app (on one domain) to securely interact with your FastAPI backend (on another domain).

By default, browsers block requests made from a frontend domain (like http://localhost:3000) to a backend domain (like http://localhost:8000) due to security reasons (Same-Origin Policy).

This becomes a problem in almost every real-world app where:

  • You build a frontend in React, Vue, or Angular
  • Your API runs on a different domain, subdomain, or port
  • You want them to talk to each other

Without proper CORS configuration, you’ll see errors like:

Access to fetch at 'http://localhost:8000/data' from origin 'http://localhost:3000' has been blocked by CORS policy

FastAPI’s Solution: CORSMiddleware

FastAPI (via Starlette) provides a built-in CORS middleware that makes this super easy to configure.

from fastapi import FastAPI 
from fastapi.middleware.cors import CORSMiddleware 
 
app = FastAPI() 
 
# Allowed origins (can be specific URLs or wildcards) 
origins = [ 
    "http://localhost", 
    "http://localhost:3000",  # your frontend dev server 
    "https://yourfrontenddomain.com" 
] 
 
app.add_middleware( 
    CORSMiddleware, 
    allow_origins=origins,             # allowed frontend origins 
    allow_credentials=True,            # allow cookies, headers, sessions 
    allow_methods=["*"],               # allow all HTTP methods 
    allow_headers=["*"],               # allow all headers 
)

CORS is one of those things that just works once it’s configured properly — but can cause major headaches when it’s not.

Set it up early, test it often, and keep it tight in production.

3. GZipMiddleware

Use GZipMiddleware to compress your FastAPI responses and significantly reduce the amount of data transferred over the network — especially helpful for large JSON payloads.

Modern browsers and HTTP clients support GZip compression natively. When your server sends compressed responses, the client automatically decompresses them, saving:

  • Bandwidth
  • Latency
  • Load time

This is critical for APIs that return large datasets (like lists of users, search results, or analytics reports).

FastAPI includes built-in GZip support through Starlette’s GZipMiddleware.

Here’s how easy it is to use:

from fastapi import FastAPI 
from fastapi.middleware.gzip import GZipMiddleware 
 
app = FastAPI() 
 
# Add GZip middleware 
app.add_middleware(GZipMiddleware, minimum_size=1000)

If you’re building a public-facing or data-heavy API, not using GZip is like sending your data through a traffic jam when you could fly it in a drone.

So turn it on — it’s free performance.

4. TrustedHostMiddleware

Use this middleware to protect your FastAPI app against Host header attacks, like DNS rebinding.

When a request hits your API, it includes a Host header (e.g., Host: api.example.com) to specify which domain the client is targeting.

If your app doesn’t validate that header, attackers can send requests with a fake Host, potentially tricking your app into:

  • Generating poisoned links or redirects
  • Creating unsafe URL references
  • Returning misleading error messages or content

This can lead to DNS rebinding attacks, especially in internal apps or cloud deployments.

It allows you to whitelist the domains your app should respond to. If a request comes in with an unknown Host header — it gets rejected with a 400 Bad Request.

from fastapi import FastAPI 
from starlette.middleware.trustedhost import TrustedHostMiddleware 
 
app = FastAPI() 
 
app.add_middleware( 
    TrustedHostMiddleware, 
    allowed_hosts=[ 
        "example.com",           # main domain 
        "*.example.com",         # all subdomains 
        "localhost",             # for local dev 
        "127.0.0.1"              # also valid for dev 
    ] 
)

Security isn’t just about passwords and firewalls. Sometimes, a single unvalidated header can be the weakest link.

Adding TrustedHostMiddleware is a simple step with big defensive value — especially for public APIs, dashboards, or any web-exposed service.

5. Request Logging Middleware

Logs every incoming HTTP request and outgoing response, helping you trace errors, analyze traffic, and monitor system behavior in real time.
  • Debugging: Understand exactly what the client sent and what the API responded with.
  • Monitoring: Track response times, endpoints usage, and traffic patterns.
  • Security auditing: Identify suspicious activity or unexpected usage.
  • Troubleshooting: Quickly spot failed requests or long-running endpoints.

Basic Implementation Using BaseHTTPMiddleware

from fastapi import FastAPI, Request 
from starlette.middleware.base import BaseHTTPMiddleware 
import time 
import logging 
 
# Configure basic logging 
logging.basicConfig(level=logging.INFO) 
logger = logging.getLogger(__name__) 
 
app = FastAPI() 
 
class LoggingMiddleware(BaseHTTPMiddleware): 
    async def dispatch(self, request: Request, call_next): 
        start_time = time.time() 
         
        # Log incoming request 
        logger.info(f"➡️ {request.method} {request.url.path}") 
         
        response = await call_next(request) 
         
        process_time = round((time.time() - start_time) * 1000, 2) 
         
        # Log response status and duration 
        logger.info(f"⬅️ {request.method} {request.url.path} - {response.status_code} ({process_time} ms)") 
         
        return response 
 
# Add to app 
app.add_middleware(LoggingMiddleware) 
 
@app.get("/hello") 
async def hello(): 
    return {"message": "Hello, world!"}

Example Log Output

➡️ GET /hello 
⬅️ GET /hello - 200 (1.43 ms)

Logs are your backend’s diary.
Every bug, crash, slow endpoint, or breach has likely left a trace — if you’re logging right.

Start with simple request/response logs. Then level up with structured logging, trace IDs, and observability tools.

6. SlowAPI (Rate Limiting)

Use SlowAPI to prevent abuse, brute-force attacks, and API overuse by limiting how often a client (usually by IP) can hit your endpoints.

Without rate limiting, your API is vulnerable to:

  • Spamming (accidental or intentional)
  • Brute-force attacks (e.g., login endpoints)
  • Cost spikes (especially with 3rd-party APIs or cloud functions)
  • Server overloads and denial of service

SlowAPI gives you a clean and flexible way to limit how many requests a client can make — based on IP, headers, or custom keys.

Installing SlowAPI

pip install slowapi

Basic Usage

from fastapi import FastAPI, Request 
from slowapi import Limiter, _rate_limit_exceeded_handler 
from slowapi.util import get_remote_address 
from slowapi.errors import RateLimitExceeded 
 
app = FastAPI() 
 
# Initialize Limiter 
limiter = Limiter(key_func=get_remote_address) 
app.state.limiter = limiter 
 
# Add exception handler 
app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler) 
 
@app.get("/limited") 
@limiter.limit("5/minute") 
async def limited_endpoint(request: Request): 
    return {"message": "This is a rate-limited endpoint"}

Try It with curl

curl http://localhost:8000/limited

Hit it 5 times quickly — on the 6th, you’ll get:

429 Too Many Requests

SlowAPI is one of those tools that’s easy to implement but has massive security and reliability benefits.

Don’t wait until your API is under fire — set up sensible limits today.

7. Session Middleware (Starlette)

SessionMiddleware allows you to store user-specific data on the server (like login state, user preferences, or cart items) while tracking users via signed cookies.

Even though modern APIs often use JWTs or OAuth for stateless auth, sessions still make sense when:

  • You’re building form-based authentication
  • You want user sessions stored securely on the server
  • You’re working on admin dashboards or legacy-style web apps
  • You want to avoid exposing sensitive data in tokens

Basic Setup in FastAPI

from fastapi import FastAPI, Request 
from starlette.middleware.sessions import SessionMiddleware 
 
app = FastAPI() 
 
# Add session middleware 
app.add_middleware( 
    SessionMiddleware, 
    secret_key="super-secret-key",  # 🔐 used for signing the cookie 
    session_cookie="myapp_session", # optional: custom cookie name 
    max_age=86400                   # optional: cookie expiration in seconds (1 day) 
) 
 
@app.get("/set-session") 
async def set_session(request: Request): 
    request.session["username"] = "aashish" 
    return {"message": "Session set"} 
 
@app.get("/get-session") 
async def get_session(request: Request): 
    username = request.session.get("username", "Guest") 
    return {"username": username}

Example Use Case: Login System

@app.post("/login") 
async def login(request: Request): 
    # Assume user is authenticated 
    request.session["user_id"] = "12345" 
    return {"message": "Logged in"} 
 
@app.get("/me") 
async def get_current_user(request: Request): 
    user_id = request.session.get("user_id") 
    if not user_id: 
        return {"message": "Not logged in"} 
    return {"user_id": user_id}

If you’re building a stateful web application in FastAPI — and you want simple, secure user session management without extra infrastructure — SessionMiddleware is the perfect fit.

Fast, simple, and zero dependencies beyond FastAPI and Starlette.

8. CORSMiddleware + Custom Preflight Caching

Improve frontend performance by caching CORS preflight responses using custom headers — reducing unnecessary network calls.

Whenever a browser makes a cross-origin request with:

  • A custom header (e.g., Authorization)
  • A method like PUT, PATCH, or DELETE
  • Cookies or credentials

…it first sends a preflight request:

OPTIONS /endpoint 
Access-Control-Request-Method: POST 
Access-Control-Request-Headers: Authorization

This checks if the actual request is allowed, based on your CORS rules.

If approved, the browser proceeds with the main request.

Problem: Too Many OPTIONS Requests

Browsers send a preflight OPTIONS request every time — unless told otherwise.

This causes:

  • Increased latency
  • Redundant round-trips
  • Wasted backend resources

The Fix: Custom Preflight Caching

Tell the browser:
“Hey, this preflight result is valid for a while — no need to ask again.”

We do this by setting the Access-Control-Max-Age header.

Built-in CORSMiddleware + Caching

FastAPI’s CORS middleware (from Starlette) doesn’t expose this config directly. So we need to wrap or extend it to inject our custom headers.

Custom CORSMiddleware With Max-Age

from fastapi import FastAPI, Request, Response 
from fastapi.middleware.cors import CORSMiddleware 
from starlette.middleware.base import BaseHTTPMiddleware 
 
app = FastAPI() 
 
# Step 1: Add default CORSMiddleware 
app.add_middleware( 
    CORSMiddleware, 
    allow_origins=["http://localhost:3000"], 
    allow_credentials=True, 
    allow_methods=["*"], 
    allow_headers=["*"], 
) 
 
# Step 2: Create a custom middleware to set Access-Control-Max-Age 
class PreflightCacheMiddleware(BaseHTTPMiddleware): 
    async def dispatch(self, request: Request, call_next): 
        if request.method == "OPTIONS": 
            response = await call_next(request) 
            # 🧠 Tell the browser to cache the preflight response for 10 minutes 
            response.headers["Access-Control-Max-Age"] = "600" 
            return response 
        return await call_next(request) 
 
app.add_middleware(PreflightCacheMiddleware)

Test With curl

curl -X OPTIONS http://localhost:8000/api \ 
  -H "Origin: http://localhost:3000" \ 
  -H "Access-Control-Request-Method: POST" \ 
  -H "Access-Control-Request-Headers: Authorization"

You should see:

Access-Control-Allow-Origin: http://localhost:3000 
Access-Control-Max-Age: 600

If you’re using FastAPI to power a modern frontend (like React, Vue, or Angular), adding preflight caching is a low-effort, high-impact optimization.

It makes your app feel faster — and scales more efficiently under load.

9. Sentry Middleware (Error Monitoring)

Catch exceptions, monitor performance, and get notified instantly when your FastAPI app crashes — using Sentry.

Sentry is a powerful error tracking and application monitoring platform. It helps you:

  • Trace errors and stack traces
  • Monitor performance metrics (latency, throughput)
  • Connect errors to code commits, releases, and users
  • Get real-time alerts when things break

FastAPI apps are often async, API-driven, and built for scale. That means:

  • Bugs can be harder to trace
  • Performance bottlenecks can go unnoticed
  • Uptime becomes critical

Sentry + FastAPI gives you observability “for free” — all in one dashboard.

Installation

pip install --upgrade sentry-sdk

Basic Integration with Middleware

import sentry_sdk 
from fastapi import FastAPI 
from sentry_sdk.integrations.asgi import SentryAsgiMiddleware 
 
# Initialize Sentry with your DSN 
sentry_sdk.init( 
    dsn="https://your-sentry-dsn@o0.ingest.sentry.io/0", 
    traces_sample_rate=1.0,  # enable full performance tracing (adjust in prod) 
    environment="production",  # optional 
    release="myapp@1.0.0",     # optional 
) 
 
app = FastAPI() 
 
# Add Sentry middleware 
app.add_middleware(SentryAsgiMiddleware) 
 
@app.get("/") 
def root(): 
    return {"message": "Hello Sentry"} 
 
@app.get("/error") 
def trigger_error(): 
    division_by_zero = 1 / 0 
    return {"message": "This line will not be reached"}

Custom Error Context

You can attach extra context, like user data or tags:

from sentry_sdk import set_user, set_tag 
 
@app.middleware("http") 
async def enrich_sentry(request, call_next): 
    set_user({"id": "user_123", "email": "aashish@example.com"}) 
    set_tag("env", "production") 
    response = await call_next(request) 
    return response

Advanced Config Options

sentry_sdk.init( 
    dsn="https://your-dsn", 
    environment="staging", 
    traces_sample_rate=0.2,  # sample 20% of requests 
    send_default_pii=True,   # send user details (only if you want!) 
)

Test Your Setup

curl http://localhost:8000/error

Visit your Sentry dashboard → you’ll see the exception with full traceback, request info, and environment.

Monitor Background Tasks Too

If you’re using FastAPI with Celery, RQ, or asyncio background jobs, you can manually capture errors:

from sentry_sdk import capture_exception 
 
try: 
    risky_operation() 
except Exception as e: 
    capture_exception(e)

In production, “no logs” ≠ “no bugs.”
Sentry gives you that critical observability layer — so you can catch, debug, and fix problems before your users notice.

10. Prometheus Middleware (Metrics)

Expose rich metrics from your FastAPI app — like request counts, error rates, and latency — using Prometheus and visualize them using tools like Grafana.

Prometheus helps you answer questions like:

  • How many requests is my app serving per second?
  • Which endpoints are slow?
  • How often are errors happening?
  • What’s the average latency per route?

All of this makes it easier to:

  • Diagnose issues in real-time
  • Trigger alerts on anomalies
  • Monitor performance trends
  • Ensure SLAs are met

Install Required Packages

pip install prometheus-fastapi-instrumentator

Quickstart: Adding Prometheus Middleware

from fastapi import FastAPI 
from prometheus_fastapi_instrumentator import Instrumentator 
 
app = FastAPI() 
 
# Create instrumentator instance 
instrumentator = Instrumentator().instrument(app).expose(app) 
 
@app.get("/ping") 
async def ping(): 
    return {"message": "pong"}

That’s it! Your metrics are now being tracked and exposed at:

GET /metrics

Customizing the Middleware

You can customize how metrics are grouped and labeled:

from prometheus_fastapi_instrumentator import Instrumentator 
 
instrumentator = Instrumentator( 
    should_group_status_codes=True, 
    should_ignore_untemplated=True, 
    should_respect_env_var=True, 
    env_var_name="ENABLE_METRICS", 
) 
 
instrumentator.instrument(app).expose(app, include_in_schema=False)

Example Metrics Output

# HELP http_request_duration_seconds Histogram of response duration 
http_request_duration_seconds_bucket{method="GET",handler="/ping",status="200",le="0.1"} 12.0 
http_request_duration_seconds_sum{method="GET",handler="/ping",status="200"} 1.45 
http_request_duration_seconds_count{method="GET",handler="/ping",status="200"} 12

These metrics can be scraped by Prometheus on a schedule.

Prometheus Config Example

scrape_configs: 
  - job_name: 'fastapi-app' 
    static_configs: 
      - targets: ['localhost:8000']

Make sure your FastAPI app is running and /metrics is reachable.

If you’re serious about uptime, performance, and reliability, Prometheus should be your app’s telemetry backbone.

With just a few lines of code, FastAPI + Prometheus gives you production-grade monitoring.

11. Cache Middleware

Use caching to reduce redundant processing and database queries — speeding up your FastAPI endpoints dramatically.

Imagine you have:

  • A product listing endpoint that doesn’t change every second
  • A public API serving weather or stock info
  • A report-heavy dashboard querying huge datasets

Instead of recalculating the response every time, you store and reuse it — cutting latency and server load.

Install FastAPI Cache

pip install fastapi-cache2[redis]

Basic Setup with Redis

from fastapi import FastAPI 
from fastapi_cache2 import FastAPICache 
from fastapi_cache2.backends.redis import RedisBackend 
import redis.asyncio as redis 
 
app = FastAPI() 
 
@app.on_event("startup") 
async def startup(): 
    r = redis.Redis(host="localhost", port=6379, decode_responses=True) 
    FastAPICache.init(RedisBackend(r), prefix="fastapi-cache")

Add Caching to Endpoints

from fastapi_cache2.decorator import cache 
 
@app.get("/products") 
@cache(expire=60)  # cache for 60 seconds 
async def get_products(): 
    # Simulate DB or computation 
    await asyncio.sleep(1) 
    return {"products": ["Laptop", "Phone", "Tablet"]}

Now repeated calls within 60 seconds return instantly.

Output Comparison

First request:

GET /products 
⏱️ Took 1000ms

Second request:

GET /products 
⚡ Took ~1ms (from cache)

Advanced Options

@cache(expire=300, namespace="reports", key_builder=my_custom_key_func)
  • expire: TTL in seconds
  • namespace: Logical grouping of cache entries
  • key_builder: Custom cache key function

You can even invalidate the cache manually if needed:

await FastAPICache.clear(namespace="reports")

In the world of APIs, speed is user experience.

By caching even a handful of high-traffic endpoints, you can cut latency, save compute, and scale effortlessly.

12. SQLAlchemy Session Middleware

Automatically create and teardown a SQLAlchemy session per request — the cleanest way to manage database access in FastAPI.

Without middleware, you often do this manually:

def get_db(): 
    db = SessionLocal() 
    try: 
        yield db 
    finally: 
        db.close()

While this works, it has downsides:

  • Repetitive across routes
  • Manual lifecycle handling
  • Easy to forget .close(), leading to leaked DB connections

First, Set Up SQLAlchemy

pip install sqlalchemy
from sqlalchemy import create_engine 
from sqlalchemy.orm import sessionmaker, declarative_base 
 
DATABASE_URL = "sqlite:///./test.db" 
 
engine = create_engine(DATABASE_URL, connect_args={"check_same_thread": False}) 
SessionLocal = sessionmaker(bind=engine, autoflush=False, autocommit=False) 
 
Base = declarative_base()

Create Middleware

Here’s a simple custom SQLAlchemySessionMiddleware:

from starlette.middleware.base import BaseHTTPMiddleware 
from starlette.requests import Request 
from starlette.responses import Response 
from sqlalchemy.exc import SQLAlchemyError 
 
class SQLAlchemySessionMiddleware(BaseHTTPMiddleware): 
    def __init__(self, app, db_session_factory): 
        super().__init__(app) 
        self.db_session_factory = db_session_factory 
 
    async def dispatch(self, request: Request, call_next): 
        request.state.db = self.db_session_factory() 
        try: 
            response = await call_next(request) 
            request.state.db.commit() 
        except SQLAlchemyError: 
            request.state.db.rollback() 
            raise 
        finally: 
            request.state.db.close() 
        return response

Add to FastAPI App

from fastapi import FastAPI 
 
app = FastAPI() 
app.add_middleware(SQLAlchemySessionMiddleware, db_session_factory=SessionLocal)

Accessing the DB Session in Routes

Now you can use request.state.db directly:

from fastapi import Request 
 
@app.get("/users") 
def read_users(request: Request): 
    db = request.state.db 
    users = db.query(User).all() 
    return users

Manually managing SQLAlchemy sessions in every route quickly becomes unscalable.

With SQLAlchemy Session Middleware, you get clean request isolation, less boilerplate, and rock-solid DB hygiene.

13. JWT Authentication Middleware

Use middleware to automatically validate JWTs and attach the authenticated user to the request — without repeating auth logic in every route.

FastAPI’s built-in Depends() system is great, but when your app grows, using JWT validation in middleware offers:

  • Centralized logic
  • Cleaner routes — no repetitive Depends(get_current_user)
  • Seamless request injection (e.g., request.state.user)

Dependencies

pip install python-jose

We’ll use python-jose to decode JWTs.

Setup: JWT Utility Functions

from jose import JWTError, jwt 
from datetime import datetime, timedelta 
 
SECRET_KEY = "your-secret-key" 
ALGORITHM = "HS256" 
 
def decode_jwt(token: str): 
    try: 
        payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM]) 
        return payload 
    except JWTError: 
        return None

Custom Middleware for JWT Auth

from starlette.middleware.base import BaseHTTPMiddleware 
from fastapi import Request, HTTPException, status 
 
class JWTAuthMiddleware(BaseHTTPMiddleware): 
    async def dispatch(self, request: Request, call_next): 
        auth_header = request.headers.get("Authorization") 
 
        if auth_header: 
            try: 
                scheme, token = auth_header.split() 
                if scheme.lower() != "bearer": 
                    raise ValueError("Invalid scheme") 
 
                payload = decode_jwt(token) 
                if payload is None: 
                    raise ValueError("Invalid or expired token") 
 
                # Attach user info to request state 
                request.state.user = payload 
 
            except ValueError: 
                raise HTTPException( 
                    status_code=status.HTTP_401_UNAUTHORIZED, 
                    detail="Invalid authentication credentials", 
                    headers={"WWW-Authenticate": "Bearer"}, 
                ) 
        else: 
            request.state.user = None  # Anonymous request 
 
        return await call_next(request)

Add Middleware to FastAPI

from fastapi import FastAPI 
 
app = FastAPI() 
app.add_middleware(JWTAuthMiddleware)

Access Authenticated User in Routes

from fastapi import Request 
 
@app.get("/me") 
async def get_me(request: Request): 
    user = request.state.user 
    if not user: 
        raise HTTPException(status_code=401, detail="Not authenticated") 
    return {"user": user}

Using JWT middleware in FastAPI keeps your codebase cleaner, DRY, and more secure — especially for large-scale or enterprise apps.

14. Request ID Middleware

Automatically attach a unique ID to every incoming request — and use it to trace logs, debug errors, and correlate services in distributed systems.

Step 1: Install python-uuid (Optional)

FastAPI doesn’t need external packages for UUIDs, but you can use ulid-py or uuid from the standard lib.

pip install ulid-py  # (optional)

Step 2: Custom Middleware for Request IDs

from starlette.middleware.base import BaseHTTPMiddleware 
from starlette.requests import Request 
from uuid import uuid4 
 
class RequestIDMiddleware(BaseHTTPMiddleware): 
    async def dispatch(self, request: Request, call_next): 
        request_id = request.headers.get("X-Request-ID", str(uuid4())) 
        request.state.request_id = request_id  # store it in request context 
 
        response = await call_next(request) 
        response.headers["X-Request-ID"] = request_id  # propagate to response 
        return response

Step 3: Add to Your FastAPI App

from fastapi import FastAPI 
 
app = FastAPI() 
app.add_middleware(RequestIDMiddleware)

Now every request has a unique ID — either provided by the client (X-Request-ID) or generated server-side.

Step 4: Use in Logs or Debugging

from fastapi import Request 
 
@app.get("/debug") 
async def debug(request: Request): 
    print(f"Request ID: {request.state.request_id}") 
    return {"request_id": request.state.request_id}

Logs now contain context like:

[INFO] [Request ID: 5a02a3fd-21a1-4ccf-a91f-d82143e3a00c] GET /debug

If you’ve ever struggled to debug “that one weird bug in production,”
Request ID Middleware is your secret weapon.

It’s small, simple, and makes observability 10x better — especially in distributed or containerized environments.

15. Helmet Middleware (Security Headers)

Add essential HTTP headers to secure your FastAPI app against common vulnerabilities like XSS, clickjacking, and MIME sniffing.

In the Node.js world, Helmet is a middleware that automatically sets secure headers like:

  • X-Content-Type-Options
  • X-Frame-Options
  • Strict-Transport-Security
  • Content-Security-Policy
  • Referrer-Policy

FastAPI doesn’t have built-in support like Helmet, but we can easily mimic its functionality using custom middleware.

Create Helmet-Like Middleware in FastAPI

from starlette.middleware.base import BaseHTTPMiddleware 
from starlette.requests import Request 
from starlette.responses import Response 
 
class HelmetMiddleware(BaseHTTPMiddleware): 
    async def dispatch(self, request: Request, call_next): 
        response: Response = await call_next(request) 
         
        # Add security headers 
        response.headers["X-Content-Type-Options"] = "nosniff" 
        response.headers["X-Frame-Options"] = "DENY" 
        response.headers["X-XSS-Protection"] = "1; mode=block" 
        response.headers["Referrer-Policy"] = "no-referrer" 
        response.headers["Permissions-Policy"] = "geolocation=(), camera=()" 
        response.headers["Strict-Transport-Security"] = "max-age=63072000; includeSubDomains; preload" 
        response.headers["Content-Security-Policy"] = ( 
            "default-src 'self'; " 
            "script-src 'self'; " 
            "style-src 'self'; " 
            "img-src 'self'; " 
            "font-src 'self'; " 
        ) 
 
        return response

Add the Middleware to FastAPI

from fastapi import FastAPI 
 
app = FastAPI() 
app.add_middleware(HelmetMiddleware)

Now, every response includes secure headers by default.

Example Output Headers

X-Content-Type-Options: nosniff 
X-Frame-Options: DENY 
X-XSS-Protection: 1; mode=block 
Referrer-Policy: no-referrer 
Permissions-Policy: geolocation=(), camera=() 
Strict-Transport-Security: max-age=63072000; includeSubDomains; preload 
Content-Security-Policy: default-src 'self'

Most attacks on web apps exploit misconfigured headers.
With Helmet-style middleware, you patch entire classes of vulnerabilities — in a single place.

It’s lightweight, powerful, and should be in every FastAPI project that touches the web.


Wrapping Up

FastAPI’s middleware system is powerful and flexible. Whether you’re building a small internal API or scaling a high-traffic SaaS product, middlewares help you standardize behaviors and enforce policies across your app.

Start small — add logging and CORS. Then layer in security, rate-limiting, and observability as your app matures.

Let me know in the comments:
Which middleware do you always include in your FastAPI projects?


If you enjoyed this, follow me for more FastAPI and Python content — I drop practical tips weekly!

Like, clap, and share if this helped you.

Photo by Kevin Canlas on Unsplash