Middleware looks like a list of independent features until one layer needs context from another. An oversized request should still receive a request ID. An exception should still produce a duration log. CORS headers may need to appear on error responses.
Order is part of the behavior.
def setup_middleware(app: FastAPI, settings: Settings) -> None:
app.add_middleware(RequestContextMiddleware)
app.add_middleware(GZipMiddleware, minimum_size=1024)
app.add_middleware(
CORSMiddleware,
allow_origins=settings.cors_origins,
expose_headers=['X-Request-Id'],
)
app.add_middleware(
TrustedHostMiddleware,
allowed_hosts=settings.trusted_hosts,
)
app.add_middleware(BodySizeLimitMiddleware, max_bytes=settings.max_body_bytes)
Starlette wraps middleware around the application according to registration order. Verify the effective order with a test instead of trusting intuition.
Establish request context early#
The outer request layer should accept or create a correlation ID, attach it to request state, and make it available to logs. Its finally block records status and duration even when deeper code fails.
Echo the ID in the response so a client report can be joined to server evidence.
Limit bodies before buffering#
Checking Content-Length handles the cheap path, but clients can use chunked transfer without that header. A pure ASGI middleware can count received bytes and disconnect when the limit is exceeded without buffering the entire payload.
Return a consistent 413 error. Body limits protect memory and provider costs, especially for AI endpoints.
Configure CORS and hosts narrowly#
CORS is a browser policy, not authentication. Allow only known origins when credentials are enabled. Trusted-host checks protect against invalid Host headers and should use deployment-specific values.
Compress responses, not streams blindly#
Compression helps ordinary JSON above a useful threshold. Streaming protocols have latency and buffering considerations; verify their behavior through the actual proxy chain.
Test middleware as a stack: normal request, exception, oversized known-length body, oversized streamed body, rejected host, and preflight request. Individual classes can be correct while their composition is not.