Understanding Middleware

Difficulty: Advanced

Middleware is a framework of hooks into Django's request/response processing. Each middleware component is a callable that processes every request before it reaches the view and every response before it returns to the client. Middleware forms a layered architecture - like an onion, requests pass through middleware layers inward to the view, and responses pass back outward.

Django's MIDDLEWARE setting is a list of middleware classes. The order matters significantly: during request processing, middleware is executed top to bottom. During response processing, it is executed bottom to top. This means the first middleware in the list is the outermost layer - it processes the request first and the response last.

Django ships with several essential middleware. SecurityMiddleware handles HTTPS redirects and security headers. SessionMiddleware enables session support. CommonMiddleware handles URL trailing slashes and static file serving. CsrfViewMiddleware provides CSRF protection. AuthenticationMiddleware attaches request.user. MessageMiddleware enables the messages framework.

Each middleware component can perform actions at several points in the request/response cycle. process_request (called before the view) can modify the request or return an early response. process_view (called just before the view function) receives the view function and its arguments. process_response (called after the view) can modify the response. process_exception (called when the view raises an exception) can handle errors.

Middleware ordering is critical. AuthenticationMiddleware must come after SessionMiddleware because it depends on session data. CsrfViewMiddleware should come before any middleware that modifies the response body. SecurityMiddleware should be first to apply security headers to all responses. Misarranging middleware can cause subtle bugs or security vulnerabilities.

Middleware is ideal for cross-cutting concerns - functionality that applies to many or all requests regardless of the specific view. Examples include logging, timing, authentication, CORS headers, rate limiting, request ID tracking, and content compression. If something needs to happen on every request, middleware is the right tool.

Code examples

Default Middleware Stack

# settings.py
MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',       # Security headers
    'django.contrib.sessions.middleware.SessionMiddleware', # Session support
    'django.middleware.common.CommonMiddleware',           # URL normalization
    'django.middleware.csrf.CsrfViewMiddleware',           # CSRF protection
    'django.contrib.auth.middleware.AuthenticationMiddleware', # request.user
    'django.contrib.messages.middleware.MessageMiddleware',   # Messages
    'django.middleware.clickjacking.XFrameOptionsMiddleware', # Clickjacking
]

# Processing order:
# Request:  Security -> Session -> Common -> CSRF -> Auth -> Messages -> XFrame -> View
# Response: View -> XFrame -> Messages -> Auth -> CSRF -> Common -> Session -> Security

Middleware processes requests top-to-bottom and responses bottom-to-top. Security is first/last because it needs to add headers to every response and can redirect HTTP to HTTPS before anything else runs.

Middleware Architecture

# How middleware wraps around the view:
#
# Request arrives
#   |
#   v
# SecurityMiddleware.process_request()
#   |
#   v
# SessionMiddleware.process_request()
#   |
#   v
# AuthenticationMiddleware.process_request()
#   |
#   v
# CsrfViewMiddleware.process_view()
#   |
#   v
# ==== VIEW FUNCTION ====
#   |
#   v
# CsrfViewMiddleware.process_response()
#   |
#   v
# AuthenticationMiddleware.process_response()
#   |
#   v
# SessionMiddleware.process_response()
#   |
#   v
# SecurityMiddleware.process_response()
#   |
#   v
# Response sent to client

The middleware stack forms an onion pattern. Each layer can short-circuit by returning a response early, skipping inner layers. This is how authentication middleware redirects unauthenticated users before the view runs.

New-Style Middleware Class

import time
import logging

logger = logging.getLogger(__name__)

class RequestTimingMiddleware:
    """Log the time taken to process each request."""

    def __init__(self, get_response):
        self.get_response = get_response
        # One-time setup code goes here

    def __call__(self, request):
        # Code before the view (and later middleware)
        start_time = time.time()

        # Get the response from the next middleware or view
        response = self.get_response(request)

        # Code after the view (and earlier middleware)
        duration = time.time() - start_time
        logger.info(
            f'{request.method} {request.path} - {response.status_code} '
            f'({duration:.3f}s)'
        )

        return response

New-style middleware uses __init__ and __call__. __init__ receives get_response (the next middleware or view). __call__ runs on every request - code before get_response runs before the view, code after runs after the view.

Middleware with Hooks

from django.http import HttpResponseForbidden
import logging

logger = logging.getLogger(__name__)

class AdvancedMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):
        response = self.get_response(request)
        return response

    def process_view(self, request, view_func, view_args, view_kwargs):
        """Called just before the view."""
        logger.info(f'Calling view: {view_func.__name__}')
        # Return None to continue, or HttpResponse to short-circuit
        return None

    def process_exception(self, request, exception):
        """Called when the view raises an exception."""
        logger.error(f'Exception in {request.path}: {exception}')
        # Return None to use default exception handling
        # Return HttpResponse to handle the exception
        return None

    def process_template_response(self, request, response):
        """Called for TemplateResponse objects."""
        response.context_data['middleware_message'] = 'Added by middleware'
        return response

Middleware can define hook methods for specific points in the cycle. process_view runs before the view with access to the view function. process_exception handles view exceptions. process_template_response modifies template context.

Key points

Concepts covered

Middleware, Request Processing, Response Processing, MIDDLEWARE Setting, Order