Complete Guide to FastAPI Middleware

📂 Stage: Stage 2 - Advanced Black Technology (Core) 🔗 Related chapters: FastAPI异步编程深度解析 · FastAPIexception-handling

Table of contents


1. Basic introduction to middleware

What is middleware?

You can think of middleware as an "onion skin" wrapped around the entire FastAPI application. Each request must pass through this skin before reaching the actual routing handler; and when the handler generates a response, the response passes through these skins again in reverse order before being returned to the client.

From a technical point of view, middleware is a hook function that is executed between the request and the response. It can:

  • Do some preprocessing (such as identity verification, logging) before the request reaches the route
  • Secondary processing of the response after the route generates it (e.g. compression, adding security headers)

In this way, all routes automatically gain these capabilities, and you no longer have to write the same logic repeatedly in each interface.

Position in request life cycle

The figure below shows the execution sequence of three different middlewares (logging, CORS, GZip) in the request/response pipeline. Requests are passed from top to bottom, and responses are returned from bottom to top.

sequenceDiagram
    participant Client as 客户端
    participant M1 as 中间件1(日志)
    participant M2 as 中间件2(CORS)
    participant M3 as 中间件3(GZip)
    participant Route as 路由处理函数
    Client->>M1: 请求
    M1->>M2: 请求
    M2->>M3: 请求
    M3->>Route: 请求
    Route-->>M3: 响应
    M3-->>M2: 压缩后的响应
    M2-->>M1: CORS响应
    M1-->>Client: 带日志的响应

Middleware vs Dependency Injection

FastAPI provides two ways to implement "cross-cutting concerns": Middleware and Dependency Injection. Many novices will wonder: they can both perform additional logic in requests. What is the difference?

Simply put:

  • Middleware acts on all requests and is suitable for global, common processing unrelated to business (such as logging, compression, security headers).
  • Dependency Injection is bound to specific routes on demand, which is suitable for operations closely related to routing parameters or business logic (such as permission verification, database sessions).

The specific comparison is as follows:

FeaturesMiddlewareDependency Injection
Trigger scopeAll requests automatically passedOn demand injected into routing parameters
Execution logicThose who register first process the request first and then process the responseThe order is consistent with the routing parameters
PurposeGlobal interception, compression, CORS, global logParameter extraction, single route authentication, database connection
Response controlThe response can be returned/modified in advanceThe response cannot be returned directly

Core Advantages

  1. Global Unification: Avoid writing the same logic repeatedly in each route and reduce code redundancy.
  2. Separation of concerns: Decouple logs, security, etc. from the business to make the code clearer.
  3. Flexible reuse: The same middleware can be applied to multiple projects, or even packaged into independent modules.
  4. Performance friendly: FastAPI’s middleware naturally supports asynchronousasync/await, will not block the event loop.

2. Basics of middleware development

FastAPI (the underlying layer is based on Starlette) provides two ways of writing middleware, which are suitable for scenarios of different complexity.

Method 1: Decorator middleware (lightweight and fast)

If you only need to implement a very simple function, such as counting the time consumption of each interface, and do not need to initialize the configuration or internal state, then directly use@app.middleware("http")Decorators are the fastest way.

from fastapi import FastAPI, Request
import time

app = FastAPI()

@app.middleware("http")
async def add_process_time(request: Request, call_next):
    # ── 请求到达时的预处理 ──
    start = time.perf_counter()

    # ── 将请求交给下一层(可能是下一个中间件,也可能是路由) ──
    response = await call_next(request)

    # ── 响应返回后的后处理 ──
    cost = (time.perf_counter() - start) * 1000
    response.headers["X-Process-Time"] = f"{cost:.2f}ms"

    return response

💡 Tips:call_nextResponsible for calling the next processing link, which will eventually execute the routing function. You can insert logic before and after it. This is the core pattern of middleware.

Method 2: InheritanceBaseHTTPMiddleware(suitable for complex functions)

When your middleware needs to receive initialization parameters (such as specifying which paths to skip) or maintain internal state (such as counting some indicators), it should inherit Starlette'sBaseHTTPMiddlewarekind.

from starlette.middleware.base import BaseHTTPMiddleware
from starlette.requests import Request
from starlette.responses import Response
import logging

# 配置日志
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

class SimpleLogMiddleware(BaseHTTPMiddleware):
    """带初始化参数的简单日志中间件"""
    def __init__(self, app, skip_paths: list = None):
        super().__init__(app)
        # 允许指定不需要打印日志的路径
        self.skip_paths = skip_paths or ["/health", "/docs", "/redoc"]

    async def dispatch(self, request: Request, call_next) -> Response:
        # 跳过指定路径
        if request.url.path in self.skip_paths:
            return await call_next(request)

        # 记录请求信息
        logger.info(f"📥 {request.method} {request.url.path}")
        response = await call_next(request)
        # 记录响应状态
        logger.info(f"📤 {request.method} {request.url.path}{response.status_code}")
        return response

# 注册中间件,同时传入自定义参数
app.add_middleware(SimpleLogMiddleware, skip_paths=["/metrics", "/docs"])

📌 Note:dispatchThe function of the method is exactly the same as the decorator version, but the writing method is different. you candispatchAccess securely withinselfto read the initialization configuration.


Three. Commonly used official/built-in middleware

The FastAPI/Starlette ecosystem has built-in some very practical middleware. Please give priority to using them when developing to avoid reinventing the wheel.

1. CORS cross-domain middleware

CORSMiddlewareIt is a middleware officially maintained by FastAPI and specifically solves the cross-domain restrictions of browsers. Proper configuration can avoid a series of strange front-end and back-end joint debugging problems.

from fastapi.middleware.cors import CORSMiddleware

app.add_middleware(
    CORSMiddleware,
    # ✅ 明确指定允许的域名,不要用通配符 *
    allow_origins=[
        "https://daomanpy.com",
        "https://www.daomanpy.com",
        "http://localhost:3000",  # 开发环境临时加
    ],
    # ✅ 允许携带 Cookie(必须配合明确的 allow_origins)
    allow_credentials=True,
    # ✅ 限制允许的 HTTP 方法
    allow_methods=["GET", "POST", "PUT", "DELETE", "OPTIONS"],
    # ✅ 限制允许的请求头
    allow_headers=["Content-Type", "Authorization", "X-Request-ID"],
    # ✅ 暴露给前端的响应头
    expose_headers=["X-Process-Time", "X-Request-ID"],
    # ✅ 预检请求缓存 1 小时,减少重复请求
    max_age=3600,
)

⚠️ Safety reminder: Do not use in production environmentallow_origins=["*"], especially if you also setallow_credentials=True- This will cause the browser to reject the cross-origin request outright.

2. GZip response compression middleware

GZipMiddlewareFrom Starlette, it can automatically compress text-based responses such as JSON, HTML, JS, etc., significantly reducing network transmission volume. Compression ratios can often reach over 70%.

from starlette.middleware.gzip import GZipMiddleware

# 仅压缩大小超过 1000 字节的响应,避免对小数据做无谓的 CPU 开销
app.add_middleware(GZipMiddleware, minimum_size=1000)

Configuration is extremely simple, typically requiring just one line of code to enable global compression.


IV. Advanced Custom Middleware Implementation

After mastering the basic usage, let's look at some custom middleware that are very practical in real business.

1. Security header middleware

Security organizations such as OWASP recommend adding a series of security headers to responses to protect against common web attacks. We can add these headers uniformly through a middleware.

class SecurityHeadersMiddleware(BaseHTTPMiddleware):
    async def dispatch(self, request: Request, call_next) -> Response:
        response = await call_next(request)
        # 添加安全头部
        response.headers.update({
            "X-Content-Type-Options": "nosniff",   # 禁止浏览器自动猜测 MIME 类型
            "X-Frame-Options": "DENY",             # 禁止被 iframe 嵌入
            "X-XSS-Protection": "1; mode=block",   # 开启 XSS 防护
            "Referrer-Policy": "strict-origin-when-cross-origin",  # 限制 Referrer 信息
        })
        return response

app.add_middleware(SecurityHeadersMiddleware)

In this way, no matter which interface returns the response, these protection headers will automatically be included.

2. Unified error handling middleware

In large projects, we usually catch all exceptions that are not handled by the routing layer and convert them into structured JSON error objects to avoid exposing internal stack details to the client.

from fastapi.exceptions import RequestValidationError
from starlette.exceptions import HTTPException as StarletteHTTPException
from starlette.responses import JSONResponse
import traceback
import os

DEBUG = os.getenv("ENVIRONMENT") != "production"

class UnifiedErrorHandler(BaseHTTPMiddleware):
    async def dispatch(self, request: Request, call_next) -> Response:
        try:
            return await call_next(request)
        # FastAPI 参数校验失败
        except RequestValidationError as e:
            return JSONResponse(
                status_code=422,
                content={"code": 422, "msg": "参数验证失败", "detail": e.errors()}
            )
        # HTTP 协议层面异常(如 404)
        except StarletteHTTPException as e:
            return JSONResponse(
                status_code=e.status_code,
                content={"code": e.status_code, "msg": e.detail}
            )
        # 其他未捕获的服务器内部错误
        except Exception as e:
            if DEBUG:
                return JSONResponse(
                    status_code=500,
                    content={"code": 500, "msg": "内部错误", "traceback": traceback.format_exc()}
                )
            else:
                return JSONResponse(
                    status_code=500,
                    content={"code": 500, "msg": "服务器开小差了,请稍后再试"}
                )

app.add_middleware(UnifiedErrorHandler)

🔒 Recommendations for production environment:DEBUGThe mode is controlled by environment variables. The online environment must hide detailed error stacks to prevent leakage of sensitive code information.


5. Best Practices in Production Environment

Although middleware is good, it can cause trouble if used incorrectly. A few core principles of practice are summarized below.

1. Control the registration sequence of middleware

Requests will be made as perapp.add_middleware()The calling sequence passes through each middleware in turn; the response passes in the reverse direction. So the order is crucial:

  • CORS middleware is usually placed in the front position in order to intercept and process the browser's preflight request as early as possible (OPTIONS)。
  • Error handling middleware must be placed at the end so that exceptions thrown by all middleware before it and inside the route can be caught.
# ✅ 正确的注册顺序
app.add_middleware(SecurityHeadersMiddleware)   # 安全头最先注入
app.add_middleware(CORSMiddleware, **cors_config)
app.add_middleware(GZipMiddleware, minimum_size=1000)
app.add_middleware(PerformanceMonitoringMiddleware)  # 假设已定义
app.add_middleware(UnifiedErrorHandler)               # exception-handling守在最后

2. Avoid performance pitfalls in middleware

  • Do not perform time-consuming operations: such as checking the database in middleware and calling external APIs, unless this is a necessary global security check. If you must do it, make sure it is done asynchronously.
  • Quickly skip irrelevant paths: Yes/docs/healthWait for the route to return as soon as possiblecall_next, to avoid wasting computing resources.
  • KEEP ASync: All IO operations should useasync/await, otherwise it will block the entire event loop and bring down the entire application.

6. Summary

FastAPI middleware is a powerful tool for realizing global cross-cutting concerns. Proper use of it can greatly improve application security, performance and code maintainability. Finally, let’s review the key points:

  1. Prefer official/built-in middleware (CORS, GZip), they are proven and ready to use out of the box.
  2. Lightweight function recommended@app.middleware("http")Decorator, complex function is inheritedBaseHTTPMiddleware
  3. Strictly control the registration sequence and useskip_pathsetc. to filter paths that do not need to be processed.
  4. Fully test the production environment and be careful to avoid synchronization blocking and hiding internal error details.

After mastering these skills, you can build a robust and efficient FastAPI application by combining different middleware like building blocks.


🔗 Extended reading