Django Middlewares: A Comprehensive Guide
Introduction
Django middlewares are a way to process requests and responses globally before they reach the view or after they leave the view. They provide a way to add functionality to your Django application without modifying every view. Middlewares are executed in a specific order and can modify requests, responses, or both.
What are Middlewares?
Middlewares are classes that implement a specific interface and are called during the request/response cycle. They sit between the web server and your Django views, allowing you to:
- Process requests before they reach views
- Process responses before they’re sent to the client
- Add headers, modify content, or perform other operations
- Handle authentication, logging, caching, and more
Middleware Architecture
Request → Middleware 1 → Middleware 2 → ... → View → Middleware 2 → Middleware 1 → Response
Middleware Order
- Request Phase: Middlewares process the request in the order they’re defined
- View Execution: The view function/class is executed
- Response Phase: Middlewares process the response in reverse order
Built-in Django Middlewares
1. Security Middleware
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
# ... other middlewares
]
Purpose: Handles security-related headers and HTTPS redirects.
Features:
- Sets security headers (XSS protection, content type sniffing, etc.)
- Handles HTTPS redirects
- Manages secure cookies
Example Configuration:
# settings.py
SECURE_BROWSER_XSS_FILTER = True
SECURE_CONTENT_TYPE_NOSNIFF = True
SECURE_HSTS_SECONDS = 31536000
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_SSL_REDIRECT = True
2. Session Middleware
MIDDLEWARE = [
'django.contrib.sessions.middleware.SessionMiddleware',
# ... other middlewares
]
Purpose: Handles session management.
Features:
- Creates session objects
- Manages session data
- Handles session cookies
Example Usage:
# views.py
def my_view(request):
request.session['user_id'] = 123
user_id = request.session.get('user_id')
return HttpResponse("Session data set")
3. Common Middleware
MIDDLEWARE = [
'django.middleware.common.CommonMiddleware',
# ... other middlewares
]
Purpose: Handles common operations like URL normalization and APPEND_SLASH.
Features:
- Normalizes URLs
- Handles trailing slashes
- Manages common headers
4. CSRF Middleware
MIDDLEWARE = [
'django.middleware.csrf.CsrfViewMiddleware',
# ... other middlewares
]
Purpose: Protects against Cross-Site Request Forgery attacks.
Features:
- Validates CSRF tokens
- Generates CSRF tokens
- Protects forms and AJAX requests
Example Usage:
# In template
{% csrf_token %}
# In JavaScript
const csrfToken = document.querySelector('[name=csrfmiddlewaretoken]').value;
fetch('/api/endpoint/', {
method: 'POST',
headers: {
'X-CSRFToken': csrfToken,
'Content-Type': 'application/json',
},
body: JSON.stringify(data)
});
5. Authentication Middleware
MIDDLEWARE = [
'django.contrib.auth.middleware.AuthenticationMiddleware',
# ... other middlewares
]
Purpose: Handles user authentication.
Features:
- Sets
request.user - Handles authentication backends
- Manages login/logout
Example Usage:
# views.py
def protected_view(request):
if request.user.is_authenticated:
return HttpResponse(f"Hello, {request.user.username}")
else:
return HttpResponse("Please log in")
6. Message Middleware
MIDDLEWARE = [
'django.contrib.messages.middleware.MessageMiddleware',
# ... other middlewares
]
Purpose: Handles temporary messages/notifications.
Features:
- Manages flash messages
- Handles message storage
- Provides message framework
Example Usage:
# views.py
from django.contrib import messages
def my_view(request):
messages.success(request, "Operation completed successfully!")
messages.error(request, "Something went wrong!")
return redirect('home')
# In template
{% if messages %}
{% for message in messages %}
<div class="alert alert-{{ message.tags }}">
{{ message }}
</div>
{% endfor %}
{% endif %}
Creating Custom Middlewares
Basic Middleware Structure
class MyCustomMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
# Code executed before the view
print("Before view execution")
response = self.get_response(request)
# Code executed after the view
print("After view execution")
return response
Adding Middleware to Settings
# settings.py
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'myapp.middleware.MyCustomMiddleware', # Your custom middleware
]
Advanced Custom Middleware Examples
1. Request Timing Middleware
import time
from django.utils.deprecation import MiddlewareMixin
class RequestTimingMiddleware(MiddlewareMixin):
def process_request(self, request):
request.start_time = time.time()
def process_response(self, request, response):
if hasattr(request, 'start_time'):
duration = time.time() - request.start_time
response['X-Request-Duration'] = str(duration)
return response
2. User Activity Middleware
from django.utils.deprecation import MiddlewareMixin
from django.contrib.auth.models import User
from django.utils import timezone
class UserActivityMiddleware(MiddlewareMixin):
def process_request(self, request):
if request.user.is_authenticated:
# Update last activity
request.user.last_activity = timezone.now()
request.user.save(update_fields=['last_activity'])
3. IP Address Middleware
from django.utils.deprecation import MiddlewareMixin
class IPAddressMiddleware(MiddlewareMixin):
def process_request(self, request):
# Get real IP address
x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
if x_forwarded_for:
ip = x_forwarded_for.split(',')[0]
else:
ip = request.META.get('REMOTE_ADDR')
request.real_ip = ip
4. Custom Headers Middleware
from django.utils.deprecation import MiddlewareMixin
class CustomHeadersMiddleware(MiddlewareMixin):
def process_response(self, request, response):
response['X-Custom-Header'] = 'Custom Value'
response['X-Powered-By'] = 'Django'
return response
5. Exception Handling Middleware
import logging
from django.utils.deprecation import MiddlewareMixin
from django.http import JsonResponse
logger = logging.getLogger(__name__)
class ExceptionLoggingMiddleware(MiddlewareMixin):
def process_exception(self, request, exception):
logger.error(f"Exception in {request.path}: {str(exception)}")
if request.path.startswith('/api/'):
return JsonResponse({
'error': 'Internal server error',
'message': str(exception)
}, status=500)
return None # Let Django handle the exception normally
6. Rate Limiting Middleware
from django.utils.deprecation import MiddlewareMixin
from django.http import HttpResponseTooManyRequests
from django.core.cache import cache
import time
class RateLimitMiddleware(MiddlewareMixin):
def process_request(self, request):
# Get client IP
ip = request.META.get('REMOTE_ADDR')
cache_key = f"rate_limit_{ip}"
# Get current request count
requests = cache.get(cache_key, [])
now = time.time()
# Remove requests older than 1 minute
requests = [req for req in requests if now - req < 60]
# Check if limit exceeded (100 requests per minute)
if len(requests) >= 100:
return HttpResponseTooManyRequests("Rate limit exceeded")
# Add current request
requests.append(now)
cache.set(cache_key, requests, 60)
7. Authentication Token Middleware
from django.utils.deprecation import MiddlewareMixin
from django.contrib.auth.models import AnonymousUser
from django.contrib.auth import get_user_model
from django.core.exceptions import ObjectDoesNotExist
User = get_user_model()
class TokenAuthenticationMiddleware(MiddlewareMixin):
def process_request(self, request):
# Skip if user is already authenticated
if request.user.is_authenticated:
return
# Get token from header
auth_header = request.META.get('HTTP_AUTHORIZATION', '')
if auth_header.startswith('Bearer '):
token = auth_header.split(' ')[1]
try:
# Find user by token (assuming you have a Token model)
from rest_framework.authtoken.models import Token
token_obj = Token.objects.get(key=token)
request.user = token_obj.user
except (Token.DoesNotExist, ObjectDoesNotExist):
request.user = AnonymousUser()
Middleware Methods
Core Methods
__init__(self, get_response)
Called once when the middleware is initialized.
class MyMiddleware:
def __init__(self, get_response):
self.get_response = get_response
# One-time configuration and initialization
print("Middleware initialized")
__call__(self, request)
Called for each request.
def __call__(self, request):
# Code executed before the view
print("Before view")
response = self.get_response(request)
# Code executed after the view
print("After view")
return response
Optional Methods
process_request(self, request)
Called before the view is executed.
def process_request(self, request):
# Modify request or return response to short-circuit
if request.path.startswith('/admin/'):
# Add custom header
request.custom_header = 'admin_request'
# Return None to continue processing
return None
process_response(self, request, response)
Called after the view is executed.
def process_response(self, request, response):
# Modify response
response['X-Custom-Header'] = 'Custom Value'
return response
process_exception(self, request, exception)
Called when an exception occurs.
def process_exception(self, request, exception):
# Log exception
import logging
logger = logging.getLogger(__name__)
logger.error(f"Exception in {request.path}: {str(exception)}")
# Return None to let Django handle the exception
return None
process_template_response(self, request, response)
Called for template responses.
def process_template_response(self, request, response):
# Modify template context
if hasattr(response, 'context_data'):
response.context_data['custom_data'] = 'Custom Value'
return response
Middleware Best Practices
1. Order Matters
MIDDLEWARE = [
# Security first
'django.middleware.security.SecurityMiddleware',
# Session before authentication
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
# CSRF protection
'django.middleware.csrf.CsrfViewMiddleware',
# Authentication
'django.contrib.auth.middleware.AuthenticationMiddleware',
# Messages
'django.contrib.messages.middleware.MessageMiddleware',
# Your custom middlewares
'myapp.middleware.CustomMiddleware',
]
2. Performance Considerations
class PerformanceMiddleware:
def __init__(self, get_response):
self.get_response = get_response
# Cache expensive operations
self.cache = {}
def process_request(self, request):
# Use caching for expensive operations
cache_key = request.path
if cache_key in self.cache:
return self.cache[cache_key]
# Perform expensive operation
result = self.expensive_operation(request)
self.cache[cache_key] = result
return result
3. Error Handling
class SafeMiddleware:
def process_request(self, request):
try:
# Your middleware logic
pass
except Exception as e:
# Log error but don't break the request
import logging
logger = logging.getLogger(__name__)
logger.error(f"Middleware error: {str(e)}")
return None
4. Conditional Execution
class ConditionalMiddleware:
def process_request(self, request):
# Only execute for specific paths
if request.path.startswith('/api/'):
# API-specific logic
pass
elif request.path.startswith('/admin/'):
# Admin-specific logic
pass
else:
# General logic
pass
Testing Middlewares
Unit Testing
from django.test import RequestFactory
from django.http import HttpResponse
from myapp.middleware import MyCustomMiddleware
class TestMyCustomMiddleware:
def setUp(self):
self.factory = RequestFactory()
self.middleware = MyCustomMiddleware(lambda request: HttpResponse("OK"))
def test_middleware_processes_request(self):
request = self.factory.get('/test/')
response = self.middleware(request)
assert response.status_code == 200
assert 'X-Custom-Header' in response
Integration Testing
from django.test import TestCase, override_settings
class MiddlewareIntegrationTest(TestCase):
@override_settings(
MIDDLEWARE=[
'django.middleware.security.SecurityMiddleware',
'myapp.middleware.MyCustomMiddleware',
]
)
def test_middleware_integration(self):
response = self.client.get('/test/')
self.assertEqual(response.status_code, 200)
self.assertIn('X-Custom-Header', response)
Common Middleware Patterns
1. Request/Response Logging
import logging
from django.utils.deprecation import MiddlewareMixin
logger = logging.getLogger(__name__)
class LoggingMiddleware(MiddlewareMixin):
def process_request(self, request):
logger.info(f"Request: {request.method} {request.path}")
def process_response(self, request, response):
logger.info(f"Response: {response.status_code}")
return response
2. CORS Middleware
from django.utils.deprecation import MiddlewareMixin
class CORSMiddleware(MiddlewareMixin):
def process_response(self, request, response):
response['Access-Control-Allow-Origin'] = '*'
response['Access-Control-Allow-Methods'] = 'GET, POST, PUT, DELETE, OPTIONS'
response['Access-Control-Allow-Headers'] = 'Content-Type, Authorization'
return response
3. Maintenance Mode Middleware
from django.http import HttpResponse
from django.utils.deprecation import MiddlewareMixin
class MaintenanceModeMiddleware(MiddlewareMixin):
def process_request(self, request):
# Check if maintenance mode is enabled
if self.is_maintenance_mode():
# Allow admin users
if request.user.is_staff:
return None
return HttpResponse("Site is under maintenance", status=503)
def is_maintenance_mode(self):
# Check from database or settings
return False
Interview Questions and Answers
Q1: What are Django middlewares and how do they work?
A: Django middlewares are classes that process requests and responses globally. They sit between the web server and Django views, allowing you to add functionality without modifying every view. Middlewares are executed in order during the request phase and in reverse order during the response phase.
Q2: What is the difference between process_request and process_response?
A:
process_requestis called before the view is executed and can modify the request or return a response to short-circuit the processprocess_responseis called after the view is executed and can modify the response before it’s sent to the client
Q3: How do you create a custom middleware in Django?
A: Create a class that implements the middleware interface:
class MyMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
# Before view
response = self.get_response(request)
# After view
return response
Then add it to the MIDDLEWARE setting in settings.py.
Q4: What are some common use cases for custom middlewares?
A: Common use cases include:
- Authentication and authorization
- Request/response logging
- Rate limiting
- CORS handling
- Custom headers
- Performance monitoring
- Error handling
- Maintenance mode
Q5: How do you handle exceptions in middleware?
A: Use the process_exception method:
def process_exception(self, request, exception):
# Log the exception
logger.error(f"Exception: {str(exception)}")
# Return None to let Django handle it normally
# Or return a custom response
return None
Q6: What is the order of middleware execution?
A: Middlewares are executed in the order they’re defined in the MIDDLEWARE setting during the request phase, and in reverse order during the response phase. The order matters because each middleware can modify the request/response for subsequent middlewares.
Q7: How do you test middlewares?
A: You can test middlewares using:
- Unit tests with RequestFactory
- Integration tests with TestCase
- Manual testing by adding logging
- Django’s test client
Q8: What are the performance implications of middlewares?
A: Middlewares add overhead to every request, so they should be:
- Efficient and avoid expensive operations
- Used only when necessary
- Cached when possible
- Ordered properly to avoid redundant processing
Summary
Django middlewares provide a powerful way to add global functionality to your application. They can handle:
- Security: Authentication, CSRF protection, security headers
- Performance: Caching, compression, monitoring
- Functionality: Logging, rate limiting, custom headers
- Error Handling: Exception logging, custom error responses
Key points to remember:
- Middlewares are executed in order during request and reverse order during response
- Use the appropriate middleware methods for your use case
- Consider performance implications
- Test your middlewares thoroughly
- Follow Django’s middleware patterns and best practices
Middlewares are essential for building robust, secure, and maintainable Django applications.
Interview angle
- “How does Django middleware work?” - a chain wrapping the view: request phase runs top-down, response phase bottom-up. Order in the settings list is significant, and misordering auth relative to session is a classic bug.
- “Middleware or decorator?” - middleware for genuinely global concerns (correlation ID, timing, security headers); a decorator or mixin when it applies to specific views. Global middleware doing work only some views need is wasted on every request.
- “What’s the performance consideration?” - every middleware runs on every request, including static and health checks. Anything doing I/O in middleware multiplies across all traffic.