0
0
FastAPIframework~15 mins

Custom middleware creation in FastAPI - Deep Dive

Choose your learning style9 modes available
Overview - Custom middleware creation
What is it?
Custom middleware in FastAPI is a way to run your own code before and after each request to your web application. It acts like a middle step that can inspect, modify, or log requests and responses. This helps you add features like logging, authentication, or error handling in one place. Middleware works globally for all routes unless specified otherwise.
Why it matters
Without middleware, you would have to repeat the same code in every route to handle common tasks like checking user tokens or logging requests. This would make your code messy and hard to maintain. Middleware lets you write this code once and apply it everywhere, saving time and reducing mistakes. It also helps keep your app organized and scalable as it grows.
Where it fits
Before learning custom middleware, you should understand basic FastAPI routing and request/response handling. After mastering middleware, you can explore advanced topics like dependency injection, background tasks, and security features in FastAPI.
Mental Model
Core Idea
Middleware is a layer that wraps around your app’s request and response cycle to add extra processing steps transparently.
Think of it like...
Imagine middleware as a security checkpoint at an airport that every passenger must pass through before boarding and after landing. It checks tickets, scans bags, and logs details without passengers needing to do anything extra.
┌───────────────┐
│   Client      │
└──────┬────────┘
       │ Request
       ▼
┌───────────────┐
│   Middleware  │  ← Runs code before passing request
└──────┬────────┘
       │ Forward request
       ▼
┌───────────────┐
│   FastAPI App │
└──────┬────────┘
       │ Response
       ▼
┌───────────────┐
│   Middleware  │  ← Runs code after receiving response
└──────┬────────┘
       │ Response
       ▼
┌───────────────┐
│   Client      │
Build-Up - 7 Steps
1
FoundationUnderstanding Middleware Basics
🤔
Concept: Middleware intercepts requests and responses to add extra processing.
Middleware is a function or class that runs code before your app handles a request and after it sends a response. It can modify the request or response or perform side tasks like logging. FastAPI uses Starlette middleware under the hood, so middleware must follow a specific interface.
Result
You know middleware acts as a gatekeeper for all requests and responses in your app.
Understanding middleware as a global interceptor helps you see how to add features that affect every request without changing each route.
2
FoundationFastAPI Middleware Structure
🤔
Concept: Middleware in FastAPI is a class with an async call method receiving request and call_next.
To create middleware, define a class with an async __call__ method. This method receives the request and a call_next function to pass the request to the next step (usually your app). You can run code before and after calling call_next to handle the response.
Result
You can write a middleware class that logs request info before and after processing.
Knowing the middleware class structure is key to writing your own middleware that fits FastAPI’s expectations.
3
IntermediateCreating a Simple Logging Middleware
🤔Before reading on: do you think middleware can modify the response body or only log info? Commit to your answer.
Concept: Middleware can inspect and log request and response details without changing the app routes.
Example: from fastapi import FastAPI, Request import time class LoggingMiddleware: def __init__(self, app): self.app = app async def __call__(self, request: Request, call_next): start_time = time.time() response = await call_next(request) process_time = time.time() - start_time print(f"Request: {request.method} {request.url} took {process_time:.4f}s") return response app = FastAPI() app.middleware('http')(LoggingMiddleware(app))
Result
Every request logs its method, URL, and processing time in the console.
Seeing middleware in action with logging shows how it can add cross-cutting concerns without touching route code.
4
IntermediateModifying Responses in Middleware
🤔Before reading on: can middleware change response headers or body? Commit to your answer.
Concept: Middleware can modify response headers and body before sending back to the client.
You can change response headers or content in middleware by editing the response object before returning it. For example, adding a custom header: from fastapi import Request class HeaderMiddleware: def __init__(self, app): self.app = app async def __call__(self, request: Request, call_next): response = await call_next(request) response.headers['X-Custom-Header'] = 'MyValue' return response app.middleware('http')(HeaderMiddleware(app))
Result
All responses include the custom header 'X-Custom-Header' with value 'MyValue'.
Knowing middleware can modify responses lets you add headers or transform output globally.
5
IntermediateHandling Exceptions in Middleware
🤔Before reading on: do you think middleware can catch exceptions from routes? Commit to your answer.
Concept: Middleware can catch and handle exceptions raised by downstream routes or middleware.
Wrap the call_next call in try-except to catch errors and return custom responses: from fastapi import Request from fastapi.responses import JSONResponse class ExceptionMiddleware: def __init__(self, app): self.app = app async def __call__(self, request: Request, call_next): try: response = await call_next(request) return response except Exception as e: return JSONResponse(status_code=500, content={"detail": "Internal Server Error"}) app.middleware('http')(ExceptionMiddleware(app))
Result
If any route raises an error, the client receives a JSON error message instead of a crash.
Middleware can centralize error handling, improving app stability and user experience.
6
AdvancedChaining Multiple Middleware Classes
🤔Before reading on: does the order of middleware classes affect request processing? Commit to your answer.
Concept: Multiple middleware can be stacked, and their order affects how requests and responses flow.
You can add several middleware classes by calling app.middleware('http') multiple times. The first added middleware runs first on the request and last on the response. This creates a stack where each middleware wraps the next. Example: app.middleware('http')(MiddlewareA(app)) app.middleware('http')(MiddlewareB(app)) Request flow: MiddlewareB → MiddlewareA → App Response flow: App → MiddlewareA → MiddlewareB
Result
Middleware order controls the sequence of processing, which can affect logging, headers, or error handling.
Understanding middleware stacking prevents bugs caused by unexpected execution order.
7
ExpertPerformance and Async Considerations in Middleware
🤔Before reading on: do you think blocking code in middleware affects all requests? Commit to your answer.
Concept: Middleware must be async and non-blocking to keep FastAPI performant and responsive.
FastAPI is built on async Python. Middleware should avoid blocking calls like time.sleep or heavy CPU tasks. Use async libraries and await calls properly. Blocking middleware delays all requests because it runs before the app handles them. Profiling middleware helps find slow parts. Example of bad blocking: import time class BadMiddleware: async def __call__(self, request, call_next): time.sleep(1) # blocks event loop return await call_next(request) Better: import asyncio class GoodMiddleware: async def __call__(self, request, call_next): await asyncio.sleep(1) # non-blocking return await call_next(request)
Result
Proper async middleware keeps your app fast and able to handle many requests simultaneously.
Knowing async internals helps avoid common performance pitfalls in middleware.
Under the Hood
FastAPI middleware is built on Starlette's ASGI middleware interface. When a request arrives, it passes through the middleware stack as a chain of async calls. Each middleware receives the request and a call_next function to invoke the next middleware or the app. After the app processes the request, the response travels back through the middleware in reverse order. This layered approach allows middleware to inspect or modify both request and response objects asynchronously.
Why designed this way?
This design follows the ASGI standard for asynchronous Python web apps, enabling high concurrency and flexibility. Middleware as a chain of async callables allows composability and separation of concerns. Alternatives like synchronous middleware would block the event loop, reducing performance. The layered approach also mirrors common patterns in other web frameworks, making it familiar and extensible.
┌───────────────┐
│   Request     │
└──────┬────────┘
       │
┌──────▼───────┐
│ Middleware 1 │
└──────┬───────┘
       │
┌──────▼───────┐
│ Middleware 2 │
└──────┬───────┘
       │
┌──────▼───────┐
│   FastAPI    │
│   App Logic  │
└──────┬───────┘
       │ Response
┌──────▼───────┐
│ Middleware 2 │
└──────┬───────┘
       │
┌──────▼───────┐
│ Middleware 1 │
└──────┬───────┘
       │
┌──────▼───────┐
│   Response   │
└──────────────┘
Myth Busters - 4 Common Misconceptions
Quick: Does middleware run only once per app startup or on every request? Commit to your answer.
Common Belief:Middleware runs once when the app starts and stays in memory.
Tap to reveal reality
Reality:Middleware runs on every request and response cycle, processing each independently.
Why it matters:Thinking middleware runs once leads to incorrect assumptions about request handling and state management, causing bugs.
Quick: Can middleware access route parameters directly? Commit to your answer.
Common Belief:Middleware can read route parameters like path variables easily.
Tap to reveal reality
Reality:Middleware runs before route matching, so it cannot access route-specific parameters directly.
Why it matters:Expecting route parameters in middleware causes confusion and errors; such logic belongs in dependencies or route handlers.
Quick: Does middleware automatically handle exceptions raised in routes? Commit to your answer.
Common Belief:Middleware always catches and handles all exceptions from routes.
Tap to reveal reality
Reality:Middleware only catches exceptions if explicitly coded to do so; otherwise, exceptions propagate and may cause server errors.
Why it matters:Assuming automatic error handling can lead to unhandled exceptions and app crashes.
Quick: Is middleware order irrelevant because all middleware do the same thing? Commit to your answer.
Common Belief:Middleware order does not affect how requests and responses are processed.
Tap to reveal reality
Reality:Middleware order is crucial; the first added middleware processes requests first and responses last.
Why it matters:Ignoring middleware order can cause unexpected behavior, such as headers missing or logging out of order.
Expert Zone
1
Middleware should be stateless or carefully manage state because it is shared across all requests and users.
2
Using middleware for heavy computation or blocking calls can degrade the entire app's performance due to async event loop blocking.
3
Middleware can be combined with FastAPI dependencies for fine-grained control, but they serve different purposes and run at different stages.
When NOT to use
Avoid middleware for logic that depends on route parameters or user-specific data; use dependencies or route handlers instead. Also, do not use middleware for tasks that require per-request stateful context that is better handled by request-scoped dependencies.
Production Patterns
In production, middleware is commonly used for logging, request ID injection, CORS handling, authentication token validation, and centralized error handling. Middleware is often combined with monitoring tools and tracing systems to track request lifecycles across distributed systems.
Connections
Aspect-Oriented Programming (AOP)
Middleware implements cross-cutting concerns similar to AOP advice.
Understanding middleware as a way to inject behavior across many parts of an app helps grasp AOP concepts used in other programming paradigms.
Network Firewalls
Middleware acts like a software firewall filtering and inspecting traffic before it reaches the app.
Seeing middleware as a filter clarifies its role in security and request validation.
Assembly Line Manufacturing
Middleware layers resemble stations on an assembly line, each adding or checking something before the product moves on.
This connection helps understand the sequential and layered nature of middleware processing.
Common Pitfalls
#1Blocking synchronous code inside async middleware.
Wrong approach:import time class BadMiddleware: async def __call__(self, request, call_next): time.sleep(2) # blocks event loop return await call_next(request)
Correct approach:import asyncio class GoodMiddleware: async def __call__(self, request, call_next): await asyncio.sleep(2) # non-blocking return await call_next(request)
Root cause:Confusing synchronous blocking calls with async awaitables causes event loop blocking and poor performance.
#2Trying to access route parameters in middleware.
Wrong approach:class ParamMiddleware: async def __call__(self, request, call_next): user_id = request.path_params['user_id'] # raises KeyError return await call_next(request)
Correct approach:Use dependencies or route handlers to access path parameters, not middleware.
Root cause:Middleware runs before route matching, so path parameters are not yet available.
#3Adding middleware after app startup dynamically.
Wrong approach:app = FastAPI() @app.on_event('startup') async def add_middleware_late(): app.middleware('http')(MyMiddleware(app)) # too late
Correct approach:Add all middleware before the app starts serving requests, typically right after app creation.
Root cause:Middleware must be registered before the app starts to ensure proper request handling.
Key Takeaways
Middleware in FastAPI is a powerful way to run code before and after every request globally.
It must be async and non-blocking to keep your app fast and responsive.
Middleware runs in a stack, so the order you add them matters for request and response flow.
Middleware cannot access route-specific data like path parameters because it runs before route matching.
Use middleware for cross-cutting concerns like logging, headers, and error handling, but use dependencies for per-route logic.