# How to Create Custom FastAPI Middleware Classes for Enhanced Request Handling

> Learn to create custom FastAPI middleware classes for enhanced request handling. Explore inheriting from Starlette's BaseHTTPMiddleware or using ASGI callables with app.add_middleware or the @app.middleware decorator.

- Repository: [Sebastián Ramírez/fastapi](https://github.com/tiangolo/fastapi)
- Tags: how-to-guide
- Published: 2026-02-16

---

**You can implement custom FastAPI middleware classes by inheriting from Starlette's `BaseHTTPMiddleware` or creating ASGI-compatible callables, then registering them via `app.add_middleware()` or the `@app.middleware("http")` decorator.**

FastAPI inherits its middleware architecture from Starlette, building an ASGI middleware stack that wraps every incoming request and outgoing response. When you create custom FastAPI middleware classes, you tap into this stack to execute logic before and after your route handlers run. This guide demonstrates how to construct, register, and optimize middleware using patterns found in the `tiangolo/fastapi` source code.

## Understanding the FastAPI Middleware Architecture

FastAPI constructs its middleware pipeline in `FastAPI.build_middleware_stack` located in [`fastapi/applications.py`](https://github.com/tiangolo/fastapi/blob/main/fastapi/applications.py). The method assembles a nested structure where each middleware wraps the next layer:

1.  **ServerErrorMiddleware** – Catches unhandled server errors at the outermost layer
2.  **User middlewares** – Your custom FastAPI middleware classes execute here
3.  **ExceptionMiddleware** – Converts exceptions to HTTP responses
4.  **AsyncExitStackMiddleware** – Guarantees resource cleanup via [`fastapi/middleware/asyncexitstack.py`](https://github.com/tiangolo/fastapi/blob/main/fastapi/middleware/asyncexitstack.py)

This ordering means middleware added first runs outermost, processing the request first and the response last.

## How to Register Custom FastAPI Middleware Classes

FastAPI provides two primary mechanisms for registering middleware, both ultimately producing ASGI-compatible callables that receive `(scope, receive, send)`.

### Using `app.add_middleware()` for Class-Based Middleware

The most common pattern for reusable components, `add_middleware` stores the class and initialization arguments in `self.user_middleware` (defined in `FastAPI.__init__` at lines 479-493 of [`fastapi/applications.py`](https://github.com/tiangolo/fastapi/blob/main/fastapi/applications.py)).

```python
from fastapi import FastAPI
from starlette.middleware.base import BaseHTTPMiddleware

class LogRequestMiddleware(BaseHTTPMiddleware):
    async def dispatch(self, request, call_next):
        print(f"Incoming {request.method} {request.url.path}")
        response = await call_next(request)
        print(f"Response status {response.status_code}")
        return response

app = FastAPI()
app.add_middleware(LogRequestMiddleware)

```

### Using the `@app.middleware` Decorator for Function-Based Middleware

For simple, one-off logic, the decorator creates a middleware function that receives the `Request` object and a `call_next` callable. This pattern appears in [`docs_src/middleware/tutorial001_py39.py`](https://github.com/tiangolo/fastapi/blob/main/docs_src/middleware/tutorial001_py39.py).

```python
import time
from fastapi import FastAPI, Request

app = FastAPI()

@app.middleware("http")
async def add_process_time_header(request: Request, call_next):
    start_time = time.perf_counter()
    response = await call_next(request)
    process_time = time.perf_counter() - start_time
    response.headers["X-Process-Time"] = str(process_time)
    return response

```

## Building Custom FastAPI Middleware Classes

When creating class-based middleware, you can choose between Starlette's convenience base class or raw ASGI compatibility for maximum control.

### Extending BaseHTTPMiddleware

The `BaseHTTPMiddleware` pattern abstracts the ASGI protocol, allowing you to work with `Request` and `Response` objects directly. Implement the `dispatch` method to process requests and responses.

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

logger = logging.getLogger(__name__)

class AuthenticationMiddleware(BaseHTTPMiddleware):
    async def dispatch(self, request: Request, call_next):
        # Pre-processing: validate headers

        token = request.headers.get("Authorization")
        if not token:
            return Response("Unauthorized", status_code=401)
        
        response = await call_next(request)
        
        # Post-processing: add security headers

        response.headers["X-Content-Type-Options"] = "nosniff"
        return response

```

### Implementing Raw ASGI Middleware

For advanced use cases like request body inspection or streaming modifications, implement the ASGI interface directly. This pattern appears in [`tests/test_custom_middleware_exception.py`](https://github.com/tiangolo/fastapi/blob/main/tests/test_custom_middleware_exception.py) with the `ContentSizeLimitMiddleware`.

```python
from fastapi import FastAPI, HTTPException, UploadFile, File, APIRouter

class ContentSizeLimitMiddleware:
    """ASGI middleware to enforce maximum request body size."""
    
    def __init__(self, app, max_content_size: int | None = None):
        self.app = app
        self.max_content_size = max_content_size

    def receive_wrapper(self, receive):
        received = 0

        async def inner():
            nonlocal received
            message = await receive()
            if message["type"] != "http.request":
                return message
            received += len(message.get("body", b""))
            if self.max_content_size and received > self.max_content_size:
                raise HTTPException(
                    status_code=422,
                    detail={
                        "name": "ContentSizeLimitExceeded",
                        "code": 999,
                        "message": "File limit exceeded"
                    }
                )
            return message

        return inner

    async def __call__(self, scope, receive, send):
        if scope["type"] != "http" or self.max_content_size is None:
            await self.app(scope, receive, send)
            return
        wrapper = self.receive_wrapper(receive)
        await self.app(scope, wrapper, send)

router = APIRouter()

@router.post("/upload")
def upload(file: UploadFile = File(...)):
    return {"filename": file.filename}

app = FastAPI()
app.include_router(router)
app.add_middleware(ContentSizeLimitMiddleware, max_content_size=2**8)

```

## Managing Middleware Execution Order

The sequence in which you add custom FastAPI middleware classes determines their execution order. As implemented in [`fastapi/applications.py`](https://github.com/tiangolo/fastapi/blob/main/fastapi/applications.py) lines 4610-4625, FastAPI constructs the stack as:

```

ServerErrorMiddleware → [User Middlewares] → ExceptionMiddleware → AsyncExitStackMiddleware

```

**Execution flow:**
- Middleware added **first** wraps the application **outermost**
- Request processing flows inward (first added sees request first)
- Response processing flows outward (first added sees response last)

```python

# Order example

app.add_middleware(LoggingMiddleware)      # Runs first on request, last on response

app.add_middleware(AuthenticationMiddleware)  # Runs second on request, second-to-last on response

app.add_middleware(GZipMiddleware)         # Runs last on request, first on response

```

## Summary

- FastAPI middleware inherits from Starlette's ASGI architecture, with the stack built in [`fastapi/applications.py`](https://github.com/tiangolo/fastapi/blob/main/fastapi/applications.py) via `build_middleware_stack`.
- Register custom FastAPI middleware classes using `app.add_middleware()` for reusable components or `@app.middleware("http")` for simple function-based logic.
- Implement middleware by extending `BaseHTTPMiddleware` for convenience, or implement `__call__(self, scope, receive, send)` for raw ASGI control over request bodies and streaming.
- Execution order follows the "onion" pattern: middleware added first runs outermost, processing requests first and responses last.
- Built-in middleware like `GZipMiddleware` and `AsyncExitStackMiddleware` demonstrate production patterns for compression and resource cleanup.

## Frequently Asked Questions

### What is the difference between `app.add_middleware()` and `@app.middleware()`?

`app.add_middleware()` accepts a class (typically inheriting from `BaseHTTPMiddleware` or implementing ASGI protocols) and stores it in `self.user_middleware` for later instantiation within the middleware stack. This approach supports configuration via constructor arguments. The `@app.middleware("http")` decorator wraps a function that receives `request` and `call_next`, creating a lightweight middleware ideal for simple cross-cutting concerns like timing or header injection.

### How do I access the request body inside custom FastAPI middleware?

Standard `BaseHTTPMiddleware` cannot easily access the request body because consuming the stream prevents downstream handlers from reading it. To inspect body content, implement a raw ASGI middleware class with a `receive_wrapper` pattern that intercepts `http.request` messages while cloning the body for the original receiver. The `ContentSizeLimitMiddleware` in [`tests/test_custom_middleware_exception.py`](https://github.com/tiangolo/fastapi/blob/main/tests/test_custom_middleware_exception.py) demonstrates this technique by wrapping the `receive` callable to monitor byte counts without breaking the ASGI flow.

### Can I raise HTTPException from within custom middleware?

Yes, but only if your middleware is positioned correctly in the stack or implements proper exception handling. The `ExceptionMiddleware` sits after user middlewares in [`fastapi/applications.py`](https://github.com/tiangolo/fastapi/blob/main/fastapi/applications.py), so raising `HTTPException` in a standard `BaseHTTPMiddleware` subclass works because Starlette's exception middleware catches it. However, in raw ASGI middleware, you must ensure exceptions propagate correctly to the `ExceptionMiddleware` or handle them within your `__call__` method to prevent ASGI server errors.

### What is the correct order for adding multiple middleware classes?

Middleware executes in the order added, following an "onion" model where the first middleware added becomes the outermost layer. In [`fastapi/applications.py`](https://github.com/tiangolo/fastapi/blob/main/fastapi/applications.py), `build_middleware_stack` constructs the sequence as `ServerErrorMiddleware` → user middlewares (in addition order) → `ExceptionMiddleware` → `AsyncExitStackMiddleware`. Therefore, add security and logging middleware first, followed by transformation middleware like GZip compression last, ensuring compression occurs after authentication checks but before the response leaves the application.