100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
Python

Django Middleware

Understand how Django middleware intercepts every request and response to implement cross-cutting concerns like authentication, security headers, and logging.

REST & AdvancedIntermediate9 min readJul 10, 2026
Analogies

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.

python
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 continue

Middleware 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

Was this page helpful?

Topics covered

#Python#DjangoStudyNotes#WebDevelopment#DjangoMiddleware#Django#Middleware#Chain#Works#StudyNotes#SkillVeris