Skip to main content
Advertisement

Middleware and CORS

Middleware intercepts every request and response to handle logging, authentication, compression, and CORS.


Basic Middleware

import time
import uuid
from fastapi import FastAPI, Request, Response

app = FastAPI()


# @app.middleware("http"): handles all HTTP requests and responses
@app.middleware("http")
async def logging_middleware(request: Request, call_next):
# Before request handling
request_id = str(uuid.uuid4())[:8]
start_time = time.perf_counter()

print(f"[{request_id}] → {request.method} {request.url.path}")

# Call the next middleware or route handler
response: Response = await call_next(request)

# After response
elapsed = (time.perf_counter() - start_time) * 1000
print(f"[{request_id}] ← {response.status_code} ({elapsed:.1f}ms)")

# Add response headers
response.headers["X-Request-ID"] = request_id
response.headers["X-Process-Time"] = f"{elapsed:.1f}ms"

return response


@app.get("/items/{item_id}")
def get_item(item_id: int):
return {"item_id": item_id}

CORS Configuration

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

app = FastAPI()

# CORS: allow cross-origin requests from browsers
app.add_middleware(
CORSMiddleware,
allow_origins=[
"http://localhost:3000", # development
"https://myapp.com", # production
],
allow_credentials=True, # allow cookies
allow_methods=["GET", "POST", "PUT", "DELETE", "PATCH"],
allow_headers=["*"],
)


# Development: allow all origins (DO NOT use in production)
def create_dev_app():
dev_app = FastAPI()
dev_app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_methods=["*"],
allow_headers=["*"],
)
return dev_app


@app.get("/api/data")
def get_data():
return {"data": "cross-origin accessible"}

GZip and TrustedHost Middleware

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

app = FastAPI()

# GZip: compress responses larger than minimum_size
app.add_middleware(GZipMiddleware, minimum_size=1000)

# TrustedHost: only allow specified hosts
app.add_middleware(
TrustedHostMiddleware,
allowed_hosts=["example.com", "*.example.com", "localhost"],
)


@app.get("/large-data")
def large_data():
return {"data": "x" * 5000} # compressed with GZip

Starlette BaseHTTPMiddleware

from fastapi import FastAPI, Request
from starlette.middleware.base import BaseHTTPMiddleware
from starlette.responses import Response, JSONResponse

app = FastAPI()


class RateLimitMiddleware(BaseHTTPMiddleware):
"""Request rate limiting (simulation)"""

def __init__(self, app, max_requests: int = 100):
super().__init__(app)
self.max_requests = max_requests
self._request_counts: dict[str, int] = {}

async def dispatch(self, request: Request, call_next) -> Response:
client_ip = request.client.host if request.client else "unknown"
count = self._request_counts.get(client_ip, 0) + 1
self._request_counts[client_ip] = count

if count > self.max_requests:
return JSONResponse(
status_code=429,
content={"detail": "Too Many Requests"},
)

response = await call_next(request)
response.headers["X-RateLimit-Remaining"] = str(self.max_requests - count)
return response


class SecurityHeadersMiddleware(BaseHTTPMiddleware):
"""Add security headers"""

async def dispatch(self, request: Request, call_next) -> Response:
response = await call_next(request)
response.headers["X-Content-Type-Options"] = "nosniff"
response.headers["X-Frame-Options"] = "DENY"
response.headers["X-XSS-Protection"] = "1; mode=block"
return response


app.add_middleware(RateLimitMiddleware, max_requests=1000)
app.add_middleware(SecurityHeadersMiddleware)


@app.get("/")
def root():
return {"message": "Hello"}

Middleware Execution Order

from fastapi import FastAPI, Request

app = FastAPI()


@app.middleware("http")
async def middleware_a(request: Request, call_next):
print("A: before request")
response = await call_next(request)
print("A: after response")
return response


@app.middleware("http")
async def middleware_b(request: Request, call_next):
print("B: before request")
response = await call_next(request)
print("B: after response")
return response


# Execution order (LIFO — last registered runs first):
# B: before request
# A: before request
# [route handler]
# A: after response
# B: after response


@app.get("/")
def root():
print("route handler")
return {}

Summary

MiddlewarePurpose
@app.middleware("http")Custom HTTP middleware
CORSMiddlewareAllow cross-origin requests
GZipMiddlewareResponse compression
TrustedHostMiddlewareHost whitelist
BaseHTTPMiddlewareStarlette-based custom middleware

Use middleware for processing common to all requests. For per-request logic, prefer dependency injection.

Advertisement