Backend / Web frameworks / Django / 07_middlewares.md

Django middleware

Updated 5 interview angles 4 min read source
On this page6
  1. The shape
  2. Order is a correctness question
  3. Async
  4. When not to write one
  5. Related
  6. Interview angle

Django middleware

A chain of callables wrapping the view. Each one sees the request on the way in and the response on the way out, so it is where genuinely global concerns live: authentication, sessions, security headers, correlation ids.

text
request  ─▶ Security ─▶ Session ─▶ Auth ─▶ view
response ◀─ Security ◀─ Session ◀─ Auth ◀─┘

Request phase runs top-down through MIDDLEWARE, response phase runs bottom-up. That single fact answers most middleware questions.

The shape

Modern Django middleware is a callable that takes get_response and returns a callable:

python
def timing_middleware(get_response):
    def middleware(request):
        start = time.monotonic()
        # get_response calls the next layer in.
        response = get_response(request)
        elapsed = time.monotonic() - start
        response["X-Elapsed-Ms"] = int(elapsed * 1000)
        return response
    return middleware

Everything before get_response(request) is the request phase; everything after is the response phase. A class with __init__(self, get_response) and __call__(self, request) is the same thing, and is worth preferring when the middleware needs configuration.

Gotcha: __init__ runs once per process, not per request. Storing request state on self leaks it between concurrent requests — one of the nastiest bugs in a Django codebase, because it only shows under load.

The extra hooks

Beyond the request/response pair, Django will call these if defined:

Hook When Use for
process_view after URL resolution act on the resolved view
process_exception view raised error reporting
process_template_response response has .render() inject context

process_view is the one people miss: it runs after routing, so it can see view_func and view_kwargs — which the plain __call__ cannot.

Order is a correctness question

MIDDLEWARE order is not stylistic. Each entry depends on what earlier ones put on the request:

  • SessionMiddleware must come before AuthenticationMiddleware, because request.user is resolved from the session.
  • AuthenticationMiddleware must come before anything reading request.user.
  • SecurityMiddleware goes near the top so its headers apply even to responses generated by later middleware.
  • GZipMiddleware is placed with care — compressing before another middleware wants to read the body defeats it.

Misordering session and auth gives AttributeError: 'WSGIRequest' object has no attribute 'user', and it is the single most common Django middleware bug.

Short-circuiting

Returning a response without calling get_response stops the chain. The remaining middleware and the view never run, but the response phase of earlier middleware still does:

python
def maintenance(get_response):
    def middleware(request):
        if settings.MAINTENANCE:
            return HttpResponse("Down", status=503)
        return get_response(request)
    return middleware

Async

Django runs middleware in whichever mode the stack is in, and adapts between sync and async when they are mixed — at a cost, because each transition wraps the call in a thread executor.

python
class MyMiddleware:
    async_capable = True
    sync_capable = False

    def __init__(self, get_response):
        self.get_response = get_response
        markcoroutinefunction(self)

    async def __call__(self, request):
        return await self.get_response(request)

Mixing sync middleware into an async stack silently reintroduces thread hops on every request. If you have gone async for throughput, check that the whole chain is async-capable.

When not to write one

Middleware runs on every request, including static files and health checks. That is the deciding question:

Applies to Use
Every request middleware
Some views decorator or mixin
One view do it in the view

Anything doing I/O — a database lookup, a cache call, an HTTP request — multiplies across all traffic. A permissions check that hits the database in middleware is a per-request query you did not need on your health endpoint.

Interview angle 5

  • “How does Django middleware work?” - a chain wrapping the view: request phase runs top-down, response phase bottom-up. Order in the settings list is significant, and misordering auth relative to session is a classic bug.
  • “Middleware or decorator?” - middleware for genuinely global concerns (correlation ID, timing, security headers); a decorator or mixin when it applies to specific views. Global middleware doing work only some views need is wasted on every request.
  • “What’s the performance consideration?” - every middleware runs on every request, including static and health checks. Anything doing I/O in middleware multiplies across all traffic.
  • “Why can’t you store state on self?” - __init__ runs once per process, not per request. Attributes set there are shared across every concurrent request, so per-request state on self leaks between users and only shows up under load.
  • “How do you stop the chain early?” - return a response without calling get_response. The view and later middleware are skipped, but the response phase of earlier middleware still runs, which is what makes a maintenance-mode middleware work.