Skip to main content
Back to articles

I built an open-source library: fastapi-middlewares

FastAPImiddlewareopen-source
I built an open-source library: fastapi-middlewares

I kept copying the same middleware setup code across every FastAPI project. Request IDs, timing, security headers, logging—same stuff, different project.

So I built FastAPI-Middlewares to fix this. It sets up production-ready middlewares in two lines of code.

What It Does

FastAPI-Middlewares gives you the core middlewares you need for production. Nothing fancy, just the basics done right.

What's included:

  • 🔍 Request ID Tracking — Unique IDs for tracing requests
  • ⏱️ Request Timing — Measure how long requests take
  • 🔒 Security Headers — OWASP-compliant headers
  • 📝 Structured Logging — JSON-formatted logs
  • 🚨 Error Handling — Clean error responses
  • Easy Setup — One function call with smart defaults

Install It

pip install fastapi-middlewares

Or with uv:

uv add fastapi-middlewares

Quick Start

from fastapi import FastAPI
from middlewares import add_essentials
 
app = FastAPI()
 
# That's it—one line
add_essentials(app)
 
@app.get("/")
def root():
    return {"message": "Hello World"}

Now your app has:

  • ✅ Request ID tracking
  • ✅ Request timing
  • ✅ Security headers
  • ✅ CORS support
  • ✅ Error handling
  • ✅ Logging
  • ✅ GZip compression

The Middlewares

1. Request ID Middleware

Adds a unique ID to each request so you can trace it through logs.

from fastapi import FastAPI, Request
from middlewares import RequestIDMiddleware
 
app = FastAPI()
app.add_middleware(RequestIDMiddleware)
 
@app.get("/users/{user_id}")
def get_user(user_id: int, request: Request):
    request_id = request.scope.get("request_id")
    return {"user_id": user_id, "request_id": request_id}

Response Headers:

X-Request-ID: 550e8400-e29b-41d4-a716-446655440000

Options:

  • header_name: Custom header name (default: "X-Request-ID")

2. Request Timing Middleware

Shows how long each request took.

from middlewares import RequestTimingMiddleware
 
app.add_middleware(RequestTimingMiddleware)

Response Headers:

X-Process-Time: 0.0245

Options:

  • header_name: Custom header name (default: "X-Process-Time")

3. Security Headers Middleware

Adds headers to protect against common attacks.

from middlewares import SecurityHeadersMiddleware
 
app.add_middleware(SecurityHeadersMiddleware)

Default Headers:

X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-XSS-Protection: 0
Referrer-Policy: strict-origin-when-cross-origin
Content-Security-Policy: ...
Strict-Transport-Security: ... (HTTPS only)

Custom Headers:

app.add_middleware(
    SecurityHeadersMiddleware,
    headers={
        "X-Frame-Options": "SAMEORIGIN",
        "X-Custom-Header": "custom-value"
    }
)

Options:

  • headers: Dict of custom headers
  • hsts_max_age: HSTS max age in seconds (default: 31536000)

4. Logging Middleware

Logs all requests and responses in a structured format.

from middlewares import LoggingMiddleware
 
app.add_middleware(
    LoggingMiddleware,
    logger_name="my_app",
    skip_paths=["/health", "/metrics"]
)

Log Output:

{
  "request_id": "550e8400-...",
  "method": "GET",
  "path": "/users/123",
  "status_code": 200,
  "process_time": "0.0245s"
}

Options:

  • logger_name: Logger name (default: "fastapi_middlewares")
  • skip_paths: Paths to skip logging (default: ["/health", "/metrics"])

5. Error Handling Middleware

Catches exceptions and returns clean JSON errors.

from middlewares import ErrorHandlingMiddleware
 
app.add_middleware(
    ErrorHandlingMiddleware,
    include_traceback=False  # Set True for development
)

Error Response:

{
  "error": "ValueError",
  "message": "Invalid user ID",
  "request_id": "550e8400-..."
}

Custom Error Handlers:

from starlette.responses import JSONResponse
 
async def handle_value_error(scope, exc):
    return JSONResponse(
        status_code=400,
        content={"error": "bad_request", "message": str(exc)}
    )
 
app.add_middleware(
    ErrorHandlingMiddleware,
    custom_handlers={ValueError: handle_value_error}
)

Options:

  • include_traceback: Include full traceback (default: False)
  • custom_handlers: Dict mapping exception types to handler functions

6. CORS Middleware

Wrapper around Starlette's CORSMiddleware.

from middlewares import add_cors
 
add_cors(
    app,
    allow_origins=["http://localhost:3000"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"]
)

7. GZip Compression

Wrapper around Starlette's GZipMiddleware.

from middlewares import add_gzip
 
add_gzip(app, minimum_size=1000)

Middleware Order Matters

Middlewares run in reverse order of how you add them. Last added = first to run.

Recommended Order:

from fastapi import FastAPI
from middlewares import (
    ErrorHandlingMiddleware,
    SecurityHeadersMiddleware,
    RequestIDMiddleware,
    RequestTimingMiddleware,
    LoggingMiddleware,
    add_cors,
    add_gzip,
)
 
app = FastAPI()
 
# Last added = First executed
add_gzip(app)                            # 7. Compress response
app.add_middleware(LoggingMiddleware)     # 6. Log request/response
app.add_middleware(RequestTimingMiddleware) # 5. Time request
app.add_middleware(RequestIDMiddleware)   # 4. Add request ID
app.add_middleware(SecurityHeadersMiddleware) # 3. Add security headers
add_cors(app)                             # 2. Handle CORS
app.add_middleware(ErrorHandlingMiddleware) # 1. Catch errors (outermost)

Why This Order?

  1. Error handling first — Catches all exceptions from other middlewares
  2. CORS early — Handles preflight requests before processing
  3. Security headers — Added to all responses
  4. Request ID — Available for all downstream middlewares and logging
  5. Timing — Measures full request duration
  6. Logging — Logs complete request/response cycle
  7. Compression last — Compresses the final response body

Complete Example

from fastapi import FastAPI, HTTPException
from middlewares import add_essentials
 
app = FastAPI(title="My API")
 
# Add all middlewares with custom config
add_essentials(
    app,
    cors_origins=["http://localhost:3000"],
    include_traceback=False,  # Set True for development
    logger_name="my_api"
)
 
@app.get("/")
def root():
    return {"message": "Hello World"}
 
@app.get("/users/{user_id}")
def get_user(user_id: int):
    if user_id < 1:
        raise ValueError("Invalid user ID")
    return {"user_id": user_id, "name": "John"}
 
@app.get("/error")
def error():
    raise HTTPException(status_code=404, detail="Not found")

Run it:

uvicorn main:app --reload

Test it:

# Check headers
curl -I http://localhost:8000/
 
# Expected headers:
# X-Request-ID: 550e8400-...
# X-Process-Time: 0.0245
# X-Content-Type-Options: nosniff
# X-Frame-Options: DENY

Wrap Up

If this saves you time, star the repo on GitHub:

github.com/mahdijafaridev/fastapi-middlewares

Want to improve it? Send a PR or open an issue.

Open source gets better when more people contribute.

Related Articles