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.
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:
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 middlewareEverything 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 onselfleaks 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:
SessionMiddlewaremust come beforeAuthenticationMiddleware, becauserequest.useris resolved from the session.AuthenticationMiddlewaremust come before anything readingrequest.user.SecurityMiddlewaregoes near the top so its headers apply even to responses generated by later middleware.GZipMiddlewareis 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:
def maintenance(get_response):
def middleware(request):
if settings.MAINTENANCE:
return HttpResponse("Down", status=503)
return get_response(request)
return middlewareAsync
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.
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.
Related
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 onselfleaks 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.