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:

  1. Request enters from the top, passing through each middleware in order
  2. The view function executes
  3. 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.user by 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 return None to let it propagate.
  • process_template_response(request, response) — runs if the response has a render() method (like TemplateResponse). 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.

pythondjangowebmiddleware

See Also