42

I have read FastAPI's documentation about middlewares (specifically, the middleware tutorial, the CORS middleware section and the advanced middleware guide), but couldn't find a concrete example of how to write a middleware class which you can add using the add_middleware function (in contrast to a basic middleware function added using a decorator) there nor on this site.

The reason I prefer to use add_middleware over the app based decorator, is that I want to write a middleware in a shared library that will be used by several different projects, and therefore I can't tie it to a specific FastAPI instance.

So my question is: how do you do it?

1

2 Answers 2

56

Since FastAPI is actually Starlette underneath, you could use BaseHTTPMiddleware, which allows one to implement a middleware class (you may want to have a look at this post as well). Below are given two variants of the same approach on how to do that, where the add_middleware() function is used to add the middleware class (Option 2 might be easier for most users). Please note that it is currently not possible to use BackgroundTasks (if that's a requirement for your task) with BaseHTTPMiddleware—check #1438 and #1640 for more details and any recent updates. Alternatives can be found in this answer and this answer.

Option 1

middlewares.py

from fastapi import Request

class MyMiddleware:
    def __init__(self, some_attribute: str):
        self.some_attribute = some_attribute

    async def __call__(self, request: Request, call_next):
        # do something with the request object
        content_type = request.headers.get('Content-Type')
        print(content_type)
        
        # process the request and get the response    
        response = await call_next(request)
        
        return response

app.py

from fastapi import FastAPI
from middlewares import MyMiddleware
from starlette.middleware.base import BaseHTTPMiddleware

app = FastAPI()
my_middleware = MyMiddleware(some_attribute="some_attribute_here_if_needed")
app.add_middleware(BaseHTTPMiddleware, dispatch=my_middleware)

Option 2

middlewares.py

from fastapi import Request
from starlette.middleware.base import BaseHTTPMiddleware

class MyMiddleware(BaseHTTPMiddleware):
    async def dispatch(self, request: Request, call_next):
        # do something with the request object, for example
        content_type = request.headers.get('Content-Type')
        print(content_type)
        
        # process the request and get the response    
        response = await call_next(request)
        
        return response

app.py

from fastapi import FastAPI
from middlewares import MyMiddleware

app = FastAPI()
app.add_middleware(MyMiddleware)

If you want to provide configuration options to the middleware class you should override the __init__ method, ensuring that the first argument is app, and any remaining arguments are optional keyword arguments. Example:

middlewares.py

from fastapi import Request
from starlette.middleware.base import BaseHTTPMiddleware

class MyMiddleware(BaseHTTPMiddleware):
    def __init__(self, app, some_attribute: str):
        super().__init__(app)
        self.some_attribute = some_attribute

    async def dispatch(self, request: Request, call_next):
        # do something with the request object, for example:
        content_type = request.headers.get('Content-Type')
        print(content_type)
        
        # process the request and get the response    
        response = await call_next(request)
        
        # add new header to the response
        response.headers['Custom'] = self.some_attribute
        
        return response

app.py

from fastapi import FastAPI
from middlewares import MyMiddleware

app = FastAPI()
app.add_middleware(MyMiddleware, some_attribute="some_attribute_here_if_needed")
Sign up to request clarification or add additional context in comments.

3 Comments

For anyone that uses this approach, please make sure to read the bug about using BaseHTTPMiddleware (The red box at the bottom). The middleware with starlette does not play well with background tasks. Just be warned because it caught us by suprise.
@Error-SyntacticalRemorse I don't see any red box in that link. Was the problem fixed?
@PedroA Appears it was fixed. The wayback machine shows it but it was removed in the newest link. I will leave the comment because we recently used it again with fastapi but when we put it under extreme load, the raw middleware was faster and the BaseHTTPMiddleware appeared to have a memory leak with its Depends on checks. (That last sentence was just an opinion we had after testing, not a fact). I would try to use BaseHTTPMiddleware if you can!
11

A potential workaround for the BaseHTTPMiddleware bug raised by @Error - Syntactical Remorse, which seems to work for me at least, is to use partial and use a functional approach to your middleware definition:

middleware.py

from typing import Any, Callable, Coroutine
from fastapi import Response


async def my_middleware(request: Request, call_next: Callable, some_attribute: Any) -> Response:
    request.state.attr = some_attribute  # Do what you need with your attribute
    return await call_next(request)

app.py

from functools import partial
from fastapi import FastAPI
from middleware import my_middleware


app = FastAPI()

my_custom_middleware: partial[Coroutine[Any, Any, Any]] = partial(my_middleware, some_attribute="my-app")

app.middleware("http")(my_custom_middlware)

1 Comment

I tried the same but, it is giving Nonetype is not callable for app.middleware("http")(my_custom_middlware)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.