What Middleware Is and How the Chain Works
Middleware in Django is a chain of lightweight plugin classes or functions that process every request before it reaches a view and every response before it leaves the server, defined in the MIDDLEWARE setting as an ordered list. Each middleware wraps the next one in the chain, meaning it can run code both on the way in (before calling get_response) and on the way out (after get_response returns), forming a nested call structure similar to Python decorators applied in sequence.
Cricket analogy: Middleware is like the sequence of fielding positions a ball passes through on its way to the boundary, from bowler to slip to gully, each one getting a chance to intercept or alter the outcome before it reaches the rope.
Writing a Custom Middleware
A modern Django middleware is typically a callable class with an __init__ that receives get_response and a __call__ method that runs your pre-processing code, calls get_response(request) to continue the chain, then runs post-processing code on the returned response before returning it. Middleware can also define optional hooks like process_view, process_exception, and process_template_response for more granular control over specific stages of request handling.
Cricket analogy: Writing __call__ is like a captain's pre-over field placement (before the ball is bowled) combined with a post-over huddle discussion (after the ball is bowled), both wrapped around the actual delivery.
import time
import logging
logger = logging.getLogger('django.request.timing')
class RequestTimingMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
start = time.monotonic()
response = self.get_response(request)
duration_ms = (time.monotonic() - start) * 1000
response['X-Request-Duration-ms'] = f'{duration_ms:.2f}'
if duration_ms > 500:
logger.warning('Slow request: %s took %.2fms', request.path, duration_ms)
return response
def process_exception(self, request, exception):
logger.error('Unhandled exception on %s: %s', request.path, exception)
return None # let Django's default exception handling continueMiddleware order in the MIDDLEWARE list matters: the request phase runs top-to-bottom, while the response phase runs bottom-to-top. For example, SecurityMiddleware is placed near the top so it can enforce HTTPS redirects early, while GZipMiddleware near the bottom compresses the final response on its way back out.
Built-in Middleware and Ordering Pitfalls
Django ships several essential middleware classes by default: SecurityMiddleware for HTTPS/HSTS enforcement, SessionMiddleware for request.session, AuthenticationMiddleware for request.user, and CsrfViewMiddleware for CSRF token verification. Misordering these — for example, placing AuthenticationMiddleware before SessionMiddleware — breaks the chain because AuthenticationMiddleware depends on request.session already being populated to look up the logged-in user, causing an AttributeError at request time.
Cricket analogy: It's like a ground staff putting up the sightscreen after the bowler has already started their run-up — the dependency (sightscreen ready) must come before the dependent action (bowling) or the whole delivery is compromised.
AuthenticationMiddleware must appear after SessionMiddleware in the MIDDLEWARE list because it reads request.session to resolve request.user via SESSION_KEY. Reordering these incorrectly is a common source of confusing AttributeError: 'WSGIRequest' object has no attribute 'session' errors.
- Middleware forms an ordered chain wrapping every request/response, running pre-logic top-down and post-logic bottom-up.
- A middleware class implements __init__(self, get_response) and __call__(self, request) as the minimum contract.
- Optional hooks like process_view and process_exception give finer-grained control over specific request stages.
- MIDDLEWARE list order matters: dependencies like SessionMiddleware must precede AuthenticationMiddleware.
- Built-in middleware handles cross-cutting concerns: security headers, sessions, authentication, and CSRF protection.
- Custom middleware is ideal for cross-cutting concerns like timing, logging, or adding response headers globally.
- process_exception can intercept unhandled view exceptions and return a custom response, or return None to defer.
Practice what you learned
1. In what order does the response phase of Django's middleware chain execute?
2. What two methods form the minimum required structure of a modern Django middleware class?
3. Why must SessionMiddleware appear before AuthenticationMiddleware in the MIDDLEWARE setting?
4. What does returning None from a process_exception hook signal?
5. Which built-in middleware is responsible for CSRF token verification on unsafe HTTP methods?
Was this page helpful?
You May Also Like
Django Signals
Learn how Django's signal dispatcher lets decoupled parts of an application react to model and request lifecycle events like save, delete, and request_finished.
Building APIs with Django REST Framework
Learn how Django REST Framework turns Django models and views into robust, browsable JSON APIs using serializers, viewsets, and routers.
Caching in Django
Explore Django's caching framework, from per-view and template-fragment caching to the low-level cache API and choosing a cache backend like Redis.
Related Reading
Related Study Notes in Web Development
Browse all study notesWebSockets Study Notes
Web Development · 30 topics
Web DevelopmentWebAssembly Study Notes
WebAssembly · 30 topics
Web DevelopmentgRPC Study Notes
Protocol Buffers · 30 topics
Web DevelopmentSpring Boot Study Notes
Java · 30 topics
Web DevelopmentFlask Study Notes
Python · 30 topics
Web DevelopmentNext.js Study Notes
JavaScript · 30 topics