How to use middleware with Django Ninja

A quick guide on using middleware in APIs built with Django Ninja. Learn how to add third-party middleware and how to implement your own.

Simon GurckeSimon Gurcke//2 min read

Django Ninja is a modern web framework for building APIs with Django and is heavily inspired by FastAPI. While it shares many features with FastAPI, such as automatic OpenAPI documentation and type validation, it differs in how middleware is handled.

Limitations

Django Ninja currently doesn’t have its own middleware system and instead relies on Django’s middleware, configured via the MIDDLEWARE setting. Middleware configured this way applies globally to the entire Django application, including Django Ninja APIs. Unfortunately, it’s not possible to apply middleware only to a specific Django Ninja API or router.

There is an open issue to add middleware support to Django Ninja directly, but it’s not yet clear if and when it will be implemented.

Adding middleware

To add middleware to your Django application, you simply add its fully qualified class name to the MIDDLEWARE list in your Django settings. In the below example, we’ve added the ApitallyMiddleware to the top of the list.

# settings.py
MIDDLEWARE = [
    "apitally.django.ApitallyMiddleware",  # Example
    "django.middleware.security.SecurityMiddleware",
    "django.contrib.sessions.middleware.SessionMiddleware",
    "django.middleware.common.CommonMiddleware",
    "django.middleware.csrf.CsrfViewMiddleware",
    "django.contrib.auth.middleware.AuthenticationMiddleware",
    "django.contrib.messages.middleware.MessageMiddleware",
    "django.middleware.clickjacking.XFrameOptionsMiddleware",
]

Note that the order of middleware in the MIDDLEWARE list matters. Django processes middleware in the order listed for incoming requests and in reverse order for outgoing responses. This creates an “onion” effect where each middleware wraps the next one.

For example, if you’re using authentication middleware, it should come before any middleware that depends on user information. Similarly, compression middleware like GZipMiddleware should come after middleware that might modify the response body, but before caching middleware.

The Django documentation provides detailed guidance on middleware ordering. As a general rule, place security-related middleware near the top, authentication middleware in the middle, and response-modifying middleware toward the bottom of the list.

Building your own middleware

A Django middleware is simply a Python class with an __init__ method that accepts a get_response callable, and a __call__ method that processes requests and responses.

# custom_middleware.py
from typing import Callable
from django.http import HttpRequest, HttpResponse

class CustomMiddleware:
    def __init__(self, get_response: Callable[[HttpRequest], HttpResponse]) -> None:
        self.get_response = get_response

    def __call__(self, request: HttpRequest) -> HttpResponse:
        # Code executed before the view (and later middleware)
        print(f"Processing request to {request.path}")

        response = self.get_response(request)

        # Code executed after the view (and earlier middleware)
        print(f"Response status: {response.status_code}")

        return response

You can also add optional methods like process_view, process_exception, and process_template_response for more specific hooks into Django’s request/response cycle. See the Django documentation for details.

Keep in mind that middleware runs on every request, so avoid heavy operations that could impact your API’s response times.

Finally, add your middleware to the MIDDLEWARE setting using its fully qualified class name:

# settings.py
MIDDLEWARE = [
    "myapp.middleware.CustomMiddleware",
    # ... other middleware
]