A Complete Guide to Middleware in FastAPI

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.

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
Middleware

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!"}
Python

Explanation:

  • 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
Python

Every response will now include:

X-Custom-Header: Hello from Middleware
Python

Error 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!"},
        )
Python

This 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
)
Python

GZip 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
Python

Custom 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
Python

Middleware 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
Python

Output

First middleware (Before request)
Second middleware (Before request)
Second middleware (After request)
First middleware (After request)
Python

Middleware 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
Python

Using 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"]}
Python

Middleware 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

Resource

Leave a Comment