Custom Middlewares in FastAPI
Custom middlewares in FastAPI allow you to inject your own logic before and after every request without duplicating that code in each route. They’re especially useful for handling cross-cutting concerns like logging, request timing, adding headers, simple authentication, assigning request IDs or ensuring consistent error handling.
To understand Middlewares, refer this article: Middlewares in FastAPI
Why use custom middleware?
- Avoids repetition: shared logic is written once in the middleware instead of being repeated in every route.
- Ensures consistency: same behavior (such as adding headers or handling errors) is applied across all endpoints.
- Improves observability: requests can be logged, performance can be measured and responses can be tagged in a uniform way.
- Enforces simple policies: unwanted requests can be blocked early or responses can be shaped (e.g., adding headers) before reaching the client.
Middleware in Action
To understand custom middleware better, let’s look at the order in which it runs during a request and response cycle.
- Request path: middlewares are executed in the same order they’re defined (from top to bottom) before request reaches the route handler.
- Response path: once the route sends back a response, middlewares are triggered again, but this time in reverse order (from bottom to top).
- Exceptions: if something goes wrong inside a route, the error flows back through middlewares, giving them a chance to log it, handle it or even transform the response.
Adding Custom Middleware
In FastAPI, adding your own middleware is simple just use @app.middleware("http") decorator and write a small async function. This function lets you run code before and after every request, without repeating that logic in each route
Basic Syntax
@app.middleware("http")
async def custom_middleware(request: Request, call_next):
# Runs before the request reaches the route
response = await call_next(request)
# Runs after the route handler sends back a response
return response
Explanation:
- request: incoming HTTP request (method, path, headers, body, etc.).
- call_next(request): forwards request to the next step (another middleware or the route) and returns a response.
- response: object that you can inspect or modify before sending it back to the client.
Examples of Creating Custom Middleware
Example 1: This code defines a custom middleware demo_mw that logs before and after each request, then forwards it to the route. A test / route shows it in action.
from fastapi import FastAPI
from fastapi.requests import Request
app = FastAPI()
# Custom middleware
@app.middleware("http")
async def demo_mw(request: Request, call_next):
print("Middleware, Before request")
response = await call_next(request) # Pass request to the next process
print("Middleware, After response")
return response
# A test route
@app.get("/")
def home():
return {"message": "Hello, FastAPI with Middleware!"}
Output: +If you open http://127.0.0.1:8000/, you’ll see the JSON response:

And in the terminal, you’ll see:
Middleware, Before request
Middleware, After response
This shows middleware runs around every request.
Explanation:
- @app.middleware("http"): Declares a middleware for all HTTP requests.
- request: Represents incoming request.
- call_next(request): Forwards request to the actual route (/ in this case).
- Code before call_next() runs before request is processed.
- Code after call_next() runs after response is ready.
Example 2: This code adds a logging_middleware that times each request, prints its method, URL and duration and then returns the response. A /hello route is included for testing.
import time
from fastapi import FastAPI, Request
app = FastAPI()
@app.middleware("http")
async def logging_middleware(request: Request, call_next):
start_time = time.time() # Record start time
response = await call_next(request) # Process request
process_time = time.time() - start_time # Calculate duration
print(f"Request: {request.method} {request.url} completed in {process_time:.4f} seconds")
return response
@app.get("/hello")
def hello():
return {"message": "Hello World"}
Output: If you visit /hello, you’ll see:

And in the terminal:
Request: GET http://127.0.0.1:8000/hello completed in 0.0009 seconds
It means middleware logged that a GET request was made to /hello and server took 0.0009 seconds to process and return the response.
Explanation:
- time.time(): Records the current time before the request is processed.
- After request finishes, we subtract times to get how long it took.
- request.method: Shows method like GET/POST.
- request.url: Shows which URL was requested.
- Logs this info in the terminal.
Example 3: This code defines a FastAPI app and a middleware that automatically attaches a header (X-Custom-Header) to every response, without needing to repeat code in each route.
from fastapi import FastAPI, Request
app = FastAPI()
@app.middleware("http")
async def add_custom_header(request: Request, call_next):
response = await call_next(request) # process the request
response.headers["X-Custom-Header"] = "This is a custom header"
return response
# Sample route
@app.get("/hello")
def say_hello():
return {"message": "Hello from FastAPI!"}
Output: Open your browser and go to: http://127.0.0.1:8000/hello, you should see the JSON response:

Now, Open Developer Tools in your browser -> go to Network tab. Click on request for /hello -> check Response Headers section.
You’ll find custom header added by our middleware:

Explanation
- @app.middleware("http"): Defines middleware for all HTTP requests.
- add_custom_header(): Runs before/after every request.
- call_next(request): Passes request to the next step (actual route).
- response.headers[]: Adds a new header to the response.
- /hello route: Returns a simple JSON message.