Open In App

Custom Middlewares in FastAPI

Last Updated : 23 Aug, 2025
Comments
Improve
Suggest changes
Like Article
Like
Report

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.

Python
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:

simpleCustomMiddleware_output
Output of Example 1

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.

Python
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:

loggingCustomMiddleware_Output
Output of Example 2

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.

Python
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:

AddingHeader_customMiddleware_Output2
Output of Example 3

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:

AddingHeader_customMiddleware_Output
Output

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.

Article Tags :

Explore