HomeBlog

6 Essential FastAPI Middlewares for Production - Ready Apps

7 min read
Illustration of a FastAPI middleware pipeline showing security, process time, and compression modules

A practical guide to implementing middleware in FastAPI for better performance, security, and efficiency

Summary

In this article, we'll explore how to add middleware to a FastAPI application. We'll go through three examples: adding a process time header to track how long requests take, setting up security headers to keep your app safe, and using GzipMiddleware to compress responses for faster delivery. Plus, we'll talk about why the order of these middlewares matters.

What's Middleware in FastAPI?

Middleware in FastAPI is a function that runs for every request and response. It takes the incoming request, does something with it (or not), passes it to your app, and then takes the response, does something again (or not), and sends it back. Simple, right? It's super useful for tasks that need to happen globally, like adding headers or compressing data.

In FastAPI, you create middleware with the @app.middleware("http") decorator or use built-in options like GZipMiddleware. Let's jump into our examples.

1: Process Time Header Middleware

First, let's add a middleware that tracks how long it takes to process a request. This is great for spotting slowdowns or debugging.

Here's how it works: when a request hits, we mark the start time. Then, we let the app handle it and get a response. Once that's done, we check the time again, calculate the difference, and add it as a header called X-Process-Time.

Here's the code:

import time
from fastapi import FastAPI, Request

app = FastAPI()

@app.middleware("http")
async def add_process_time_header(request: Request, call_next):
    start_time = time.perf_counter()
    response = await call_next(request)
    process_time = time.perf_counter() - start_time
    response.headers["X-Process-Time"] = str(process_time)
    return response

We use time.perf_counter() because it's more precise than time.time() for short measurements. One thing to note: if you want browsers to see this header, you might need to adjust your CORS settings with expose_headers. But that's optional and depends on your setup.

2: Security Headers Middleware

Next up, let's make your app safer with security headers. These tell browsers how to handle your site and can block common attacks like clickjacking or cross-site scripting.

Here's a middleware that adds them:

from fastapi import Request

async def add_security_headers(request: Request, call_next):
    response = await call_next(request)
    response.headers["X-Frame-Options"] = "DENY"
    response.headers["X-Content-Type-Options"] = "nosniff"
    response.headers["Strict-Transport-Security"] = "max-age=63072000; includeSubDomains"
    response.headers["Permissions-Policy"] = (
        "geolocation=(),midi=(),sync-xhr=(),microphone=(),camera=(),"
        "magnetometer=(),gyroscope=(),fullscreen=(self),payment=()"
    )
    response.headers["Referrer-Policy"] = "strict-origin-when-cross-origin"
    response.headers["Content-Security-Policy"] = (
        "default-src 'self'; "
        "script-src 'self' 'unsafe-inline' data: https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://www.google.com https://www.gstatic.com https://z.clarity.ms https://*.clarity.ms https://*.googlesyndication.com; "
        "style-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://fonts.googleapis.com https://cdnjs.cloudflare.com; "
        "img-src 'self' data: https://lh3.googleusercontent.com https://*.twimg.com https://*.fbcdn.net https://*.ytimg.com https://*.fna.fbcdn.net https://*.clarity.ms https://*.tiktokcdn.com https://*.tiktokcdn-us.com image/; "
        "font-src 'self' data: https://fonts.gstatic.com; "
        "object-src 'none'; "
        "form-action 'self'; "
        "frame-src 'self' https://www.google.com https://www.gstatic.com; "
        "connect-src 'self' https://*.clarity.ms https://z.clarity.ms; "
        "upgrade-insecure-requests"
    )
    return response

That's a lot, so let's break it down:

You might need to tweak these based on your app - say, if you use external scripts or images, adjust the CSP. But this is a solid base.

3: Trusted Host Middleware

Next, let's lock down which domains can access your API using TrustedHostMiddleware. This is a crucial security step to prevent Host Header attacks.

Why It's Essential: A malicious actor could send a request with a fake Host header. If your application uses this header to generate URLs (like in password reset emails), it could be tricked into sending users to a malicious site. TrustedHostMiddleware validates the Host header against a list of allowed domains and will raise an error if it doesn't match.

Here's how to add it:

from fastapi import FastAPI
from starlette.middleware.trustedhost import TrustedHostMiddleware

app = FastAPI()

app.add_middleware(
    TrustedHostMiddleware, allowed_hosts=["example.com", "*.example.com"]
)

Make sure to replace example.com with your actual domain. You can use wildcards for subdomains.

4: CORS Middleware for Cross-Origin Requests

If your frontend is served from a different domain than your backend (like localhost:3000 for a React dev server and localhost:8000 for FastAPI), you'll run into Cross-Origin Resource Sharing (CORS) issues. Browsers block these requests by default for security.

FastAPI's CORSMiddleware makes it easy to tell browsers which origins, methods, and headers are allowed.

from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware

app = FastAPI()

origins = [
    "http://localhost:3000",
    "https://your-frontend-domain.com",
]

app.add_middleware(
    CORSMiddleware,
    allow_origins=origins,
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)
  • allow_origins: A list of domains that are allowed to make requests.
  • allow_credentials: Allows cookies to be included in cross-origin requests.
  • allow_methods and allow_headers: Use ["*"] to allow all standard methods and headers, or specify them for tighter security.

For a more detailed example of using CORS when serving a frontend application, see my guide on Serving a React Frontend Application with FastAPI.

5: Advanced GzipMiddleware for Compression

Finally, let's speed things up with compression. The GZipMiddleware compresses responses if the client says it's cool with GZip (via the Accept-Encoding: gzip header). Smaller responses mean faster load times.

Here's how to use it:

from fastapi import FastAPI
from fastapi.middleware.gzip import GZipMiddleware

app = FastAPI()

app.add_middleware(GZipMiddleware, minimum_size=1000, compresslevel=5)

The minimum_size=1000 means it only compresses responses over 1000 bytes - smaller ones aren't worth the effort. The compresslevel=5 is a middle ground: 1 is fast but less compression, 9 is slow but shrinks more. Five works well for most cases.

When a client asks for GZip, this middleware compresses the response body and adds Content-Encoding: gzip. It's a quick win for performance, especially on slow networks.

6: Custom Exception Handling Middleware

What happens when an unexpected error occurs? By default, FastAPI returns a generic 500 Internal Server Error. A custom exception middleware lets you catch these errors, log them, and return a clean JSON response.

import logging
from fastapi import FastAPI, Request
from starlette.responses import JSONResponse

app = FastAPI()
logger = logging.getLogger(__name__)

@app.middleware("http")
async def exception_handler_middleware(request: Request, call_next):
    try:
        return await call_next(request)
    except Exception as e:
        # Log the exception for debugging
        logger.error(f"Unhandled exception: {e}", exc_info=True)
        # Return a standardized error response
        return JSONResponse(
            status_code=500,
            content={"message": "An internal server error occurred."},
        )

Important: This middleware should be placed very early in the chain (but after CORS) to catch errors from other middlewares as well.

Why Middleware Order Matters

Now, how do we put these together? And does the order matter?

Yes, it does.

In FastAPI (and Starlette, which it's built on), middlewares run in the order you add them for requests and reverse order for responses. So, the first one added is the first to see the request and the last to touch the response.

It's crucial to add CORSMiddleware first, before any other middleware. This ensures that it can properly handle CORS preflight requests and add the necessary headers to all responses, including errors generated by other middleware. Security middleware like TrustedHostMiddleware should come next.

Imagine this:

  1. CORS middleware processes the request, passes it on.
  2. Trusted Host middleware validates the Host header.
  3. Security headers middleware prepares to add headers to the response.
  4. Process time starts its timer, passes it on.
  5. GZip does nothing yet, passes it to the app.
  6. The app makes a response.
  7. GZip compresses it.
  8. Process time adds its header.
  9. Security headers adds its headers.
  10. Trusted Host does nothing on the response.
  11. CORS middleware adds its headers.

Here's how to set it up:

from fastapi import FastAPI
from starlette.middleware.base import BaseHTTPMiddleware
from starlette.middleware.trustedhost import TrustedHostMiddleware
from fastapi.middleware.cors import CORSMiddleware
from fastapi.middleware.gzip import GZipMiddleware

app = FastAPI()

# CORS middleware first!
app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"], # Adjust for production
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

# Then Trusted Host
app.add_middleware(
    TrustedHostMiddleware, allowed_hosts=["example.com", "*.example.com"]
)

# Then other middlewares
app.add_middleware(BaseHTTPMiddleware, dispatch=add_security_headers)

# Then process time
app.add_middleware(BaseHTTPMiddleware, dispatch=add_process_time_header)

# GZip last
app.add_middleware(GZipMiddleware, minimum_size=1000)

Notice we use BaseHTTPMiddleware for custom functions - it's a wrapper FastAPI needs. Built-in ones like CORSMiddleware, TrustedHostMiddleware, and GZipMiddleware go straight in.

This order works because security and process time only add headers, and GZip compresses the body. Headers aren't compressed, so adding them after GZip is fine. But if a middleware needed to read the response body, you'd put it before GZip to see the uncompressed version.

Think about what each middleware does and plan the sequence.

Wrapping Up: Middleware Isn't Optional - It's Essential

Middleware is more than just a technical detail in FastAPI; it's what elevates a simple API into a secure, resilient, and production-grade service. While FastAPI gives you the tools to build fast endpoints, the right middleware stack provides the security, performance, and observability needed to run them confidently in the wild.

We've covered how to:

  • Secure your application with TrustedHostMiddleware and strong security headers.
  • Handle cross-origin requests correctly with CORSMiddleware.
  • Improve performance by compressing responses with GZipMiddleware.
  • Gain insights by tracking request times.

Think of this middleware stack as a solid foundation for any serious FastAPI project. By understanding how these components work together - and why their order matters - you're not just adding features. You're building a better, safer, and more efficient application.

About the Author

David Muraya is a Solutions Architect specializing in Python, FastAPI, and Cloud Infrastructure. He is passionate about building scalable, production-ready applications and sharing his knowledge with the developer community. You can connect with him on LinkedIn.

Related Blog Posts

Enjoyed this blog post? Check out these related posts!

6 Essential FastAPI Middlewares for Production-Ready Apps

6 Essential FastAPI Middlewares for Production-Ready Apps

A guide to the 6 key middlewares for building secure, performant, and resilient FastAPI applications.

Read More..

FastAPI Tutorial: A Complete Guide for Beginners

FastAPI Tutorial: A Complete Guide for Beginners

A step-by-step guide to building your first API with Python and FastAPI, from installation to production-ready concepts.

Read More..

Simple CI/CD for Your FastAPI App with Google Cloud Build and Cloud Run

Simple CI/CD for Your FastAPI App with Google Cloud Build and Cloud Run

Push code, deploy automatically: A simple CI/CD guide for your web app.

Read More..

Running Database Migrations with Alembic in Google Cloud Build

Running Database Migrations with Alembic in Google Cloud Build

How to Organize and Load FastAPI Settings from a .env File Using Pydantic v2

Read More..

Contact Me

Have a project in mind? Send me an email at hello@davidmuraya.com and let's bring your ideas to life. I am always available for exciting discussions.

© 2025 David Muraya. All rights reserved.