Architecting Cross-Cutting Concerns with Django Middleware
Learn how to implement Django Middleware using the Russian Doll pattern to handle cross-cutting concerns like logging and authentication without polluting your views.
20 Jul 2025, 21:09 UTC

The Problem: Logic Leakage in Views
When implementing requirements like request logging, authentication checks, or security headers, developers often repeat the same logic across multiple views. This leads to "logic leakage," where infrastructure concerns pollute business logic, making the codebase harder to maintain and increasing the risk of inconsistent security enforcement.
The solution is the Django Middleware system. Middleware allows you to isolate cross-cutting concerns into a centralized chain that processes every request and response. The key takeaway is that middleware is not just a plugin list, but a Russian Doll architecture: each layer wraps the next, meaning the first middleware in your settings is the first to touch the request and the last to touch the response.
The Smallest Suitable Design
To implement a custom concern, you do not need a complex class hierarchy. The smallest viable design is a class that implements a __call__ method. This method receives the request and a get_response callable, which represents the next layer in the chain.
For requirements that only need to trigger under specific conditions (like handling an exception or modifying a view's output), you can optionally implement process_view or process_exception. However, for most logging or authentication tasks, the __call__ pattern is sufficient.
Example: Request Latency Logger
This example demonstrates how to capture the start time of a request and log the total processing time after the view has executed.
# Run this in your app's middleware.py file
import time
import logging
logger = logging.getLogger(__name__)
class LatencyLoggingMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
# 1. Pre-processing: Happens before the view
start_time = time.time()
# 2. Pass the request to the next middleware/view
response = self.get_response(request)
# 3. Post-processing: Happens after the view
duration = time.time() - start_time
logger.info(f"Request to {request.path} took {duration:.2f}s")
return response
Trust and Data Boundaries
Middleware establishes the trust boundary for your application. Data entering the system is untrusted. Therefore, the order of your MIDDLEWARE list in settings.py is a critical security decision.
- Outermost Layers (Top of list): Should handle security and sanitization (e.g.,
SecurityMiddleware). These layers ensure the request is safe before it reaches authentication logic. - Intermediate Layers: Handle identity and session management (e.g.,
AuthenticationMiddleware). These layers attach theuserobject to therequest. - Innermost Layers (Bottom of list): Handle application-specific logic that depends on a known user or a sanitized request.
Risk: If you place a custom middleware that accesses request.user above the AuthenticationMiddleware, the application will raise an AttributeError because the user object has not yet been attached to the request.
Operational Checks and Verification
To verify that your middleware is executing in the expected order and capturing data, use the following methods:
- Settings Audit: Check
settings.py. The request flows from index 0 downward; the response flows from the bottom upward. - Execution Trace: Insert temporary
print()orlogger.debug()statements at the start and end of your__call__method. - Response Header Check: If your middleware adds headers, use
curl -I http://localhost:8000to verify the header is present in the final response.
Failure Modes and Design Constraints
Middleware is a high-leverage point of failure. Because it wraps every request, a single unhandled exception in a middleware layer will trigger a 500 Internal Server Error for every single page in your application, bypassing the view's internal error handling.
When to avoid Middleware
Do not use middleware if the logic only applies to a small subset of views. Instead, use Decorators or Mixins. Using middleware for rare tasks introduces unnecessary latency to every request (the "middleware tax").
Conditions for Redesign
You should move logic out of middleware and into a service layer or view decorator if:
- The logic requires complex database queries that significantly increase TTFB (Time to First Byte).
- The logic depends on URL-specific arguments (kwargs) that are only available after the URL resolver has run.
- The logic needs to be toggled on/off for specific API endpoints.
Rollback Procedure
Because middleware is configured via a list in settings.py, rolling back a faulty implementation is a state-less operation:
- Remove the middleware class string from the
MIDDLEWARElist insettings.py. - Restart the application server (e.g., Gunicorn or runserver) to clear the cached middleware chain.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.