Django Middleware — Core Concepts
The middleware pipeline
Django processes every HTTP request through a stack of middleware classes defined in MIDDLEWARE in your settings. The stack operates like nested layers:
- Request enters from the top, passing through each middleware in order
- The view function executes
- The response travels back up through the middleware in reverse order
Any middleware can short-circuit this pipeline by returning a response early — for example, redirecting unauthenticated users to a login page before the view ever runs.
How a middleware class is structured
Modern Django middleware uses a simple callable pattern:
class TimingMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
# Code here runs BEFORE the view
import time
start = time.monotonic()
response = self.get_response(request)
# Code here runs AFTER the view
duration = time.monotonic() - start
response['X-Request-Duration'] = f'{duration:.4f}'
return response
The __init__ receives get_response — a callable that represents the next middleware or the view itself. Calling self.get_response(request) passes the request down the chain. Everything before that call is “request processing”; everything after is “response processing.”
Built-in middleware you should know
Django’s default MIDDLEWARE setting includes several important classes:
- SecurityMiddleware — adds headers like
X-Content-Type-Options,X-XSS-Protection, and HSTS. Handles SSL redirects. - SessionMiddleware — attaches
request.session, a dictionary-like object backed by your session store (database, cache, or files). - CommonMiddleware — handles URL normalization (appending slashes), setting
Content-Length, and conditional GET responses. - CsrfViewMiddleware — validates CSRF tokens on POST requests to prevent cross-site attacks.
- AuthenticationMiddleware — attaches
request.userby reading the session. Without this, every request appears anonymous. - MessageMiddleware — enables the Django messages framework for one-time notifications across redirects.
Order matters
Middleware order in MIDDLEWARE is critical. AuthenticationMiddleware depends on SessionMiddleware (it reads the session to identify the user), so Sessions must come first. CsrfViewMiddleware should come after SessionMiddleware but before any middleware that accesses POST data.
A common mistake is placing custom middleware that reads request.user before AuthenticationMiddleware — the user attribute won’t exist yet, and you’ll get an AttributeError.
Hook methods beyond call
Middleware classes can define optional hook methods for finer control:
process_view(request, view_func, view_args, view_kwargs)— runs after URL routing but before the view executes. Useful for access control decisions that depend on which view was matched.process_exception(request, exception)— runs if the view raises an exception. Can return a response to handle the error or returnNoneto let it propagate.process_template_response(request, response)— runs if the response has arender()method (likeTemplateResponse). Allows modifying template context before rendering.
Practical use cases
Request logging: Capture method, path, user, and response status for every request. Pair with structured logging for production observability.
Rate limiting: Count requests per IP or user within a time window, returning 429 Too Many Requests when the limit is exceeded.
Maintenance mode: Check a flag (database, cache, or environment variable) and return a 503 page for all requests when the site is under maintenance.
Tenant identification: In multi-tenant apps, examine the request’s domain or headers to identify the tenant and attach it to the request for downstream use.
Common misconception
Developers sometimes treat middleware as the place for all cross-cutting logic. But middleware runs on every single request — including static files, health checks, and admin pages. If your logic only applies to certain views, a decorator or mixin is more targeted and efficient than middleware that checks URL patterns to decide whether to run.
The one thing to remember: Middleware is Django’s universal request/response filter — it processes everything, so use it for truly global concerns and keep view-specific logic in decorators.
See Also
- Python Django Admin Get an intuitive feel for Django Admin so Python behavior stops feeling unpredictable.
- Python Django Basics Get an intuitive feel for Django Basics so Python behavior stops feeling unpredictable.
- Python Django Celery Integration Why your Django app needs a helper to handle slow jobs in the background.
- Python Django Channels Websockets How Django can send real-time updates to your browser without you refreshing the page.
- Python Django Custom Management Commands How to teach Django new tricks by creating your own command-line shortcuts.