Skip to main content

Django Custom Middleware

Introduction

Django's middleware framework provides a powerful way to process requests and responses globally across your application. While Django includes several built-in middleware components, creating custom middleware allows you to implement specific functionality that executes during the request-response cycle.

Custom middleware can be used for a variety of purposes such as:

  • Authentication and authorization
  • Request logging and monitoring
  • Response modification
  • Performance tracking
  • Custom headers manipulation
  • Cross-origin resource sharing (CORS) handling

In this tutorial, we'll explore how to create, implement, and use custom middleware in your Django applications.

Understanding Middleware Architecture

Before diving into custom middleware, it's important to understand how Django's middleware system works:

  1. When a request comes in, Django processes it through middleware in order (top to bottom as defined in your settings)
  2. The request passes through each middleware's process_request method
  3. After the view is called, the response passes back through middleware in reverse order through their process_response methods

Since Django 1.10, middleware uses a new style based on a callable class, though the old function-based middleware is still supported.

Creating Your First Custom Middleware

Let's start by creating a simple middleware that logs information about incoming requests:

Function-Based Middleware (New Style)

First, let's create a new file called middleware.py in your Django application:

python
import time
from django.utils.deprecation import MiddlewareMixin

class RequestLogMiddleware(MiddlewareMixin):
"""Middleware to log all requests and their processing time."""

def process_request(self, request):
"""Store the start time when the request comes in."""
request.start_time = time.time()

def process_response(self, request, response):
"""Calculate and log the request duration."""
# If process_request was never called, start_time won't exist
if hasattr(request, 'start_time'):
duration = time.time() - request.start_time
print(f"Request to {request.path} took {duration:.2f}s to process.")
return response

Activating Your Middleware

To use your custom middleware, you need to add it to the MIDDLEWARE list in your project's settings.py file:

python
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.RequestLogMiddleware', # Our custom middleware
]

Now, every request to your application will be logged with its processing time.

Understanding Middleware Methods

The MiddlewareMixin provides several methods you can implement:

  1. process_request(request): Called on each request before Django decides which view to execute
  2. process_view(request, view_func, view_args, view_kwargs): Called just before Django calls the view
  3. process_exception(request, exception): Called when a view raises an exception
  4. process_template_response(request, response): Called just after the view has finished executing, if the response has a render() method
  5. process_response(request, response): Called on all responses before they're sent to the client

You don't need to implement all of these methods—only the ones you need for your specific functionality.

Practical Examples

Let's explore more real-world examples of custom middleware:

1. IP Restriction Middleware

This middleware restricts access to your application based on IP addresses:

python
from django.http import HttpResponseForbidden
from django.utils.deprecation import MiddlewareMixin

class IPRestrictionMiddleware(MiddlewareMixin):
"""Middleware to restrict access based on IP address."""

ALLOWED_IPS = ['127.0.0.1', '192.168.1.1']

def process_request(self, request):
client_ip = self.get_client_ip(request)
if client_ip not in self.ALLOWED_IPS:
return HttpResponseForbidden("Access denied")

def get_client_ip(self, request):
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')
return ip

2. Simple Authentication Middleware

This middleware checks if a user is authenticated for certain URL patterns:

python
from django.http import HttpResponseRedirect
from django.urls import reverse
from django.utils.deprecation import MiddlewareMixin
import re

class AuthenticationMiddleware(MiddlewareMixin):
"""
Middleware to check if a user is authenticated for certain URLs.
"""

def process_request(self, request):
# Exempt certain paths from authentication
exempt_urls = [r'^/login/$', r'^/public/', r'^/api/public/']

# Check if the path matches any of the exempt patterns
path = request.path_info
if any(re.match(pattern, path) for pattern in exempt_urls):
return None

# Check if the user is authenticated
if not request.user.is_authenticated:
return HttpResponseRedirect(reverse('login') + f'?next={request.path}')

3. Response Modification Middleware

This middleware adds custom headers to all responses:

python
from django.utils.deprecation import MiddlewareMixin

class CustomHeadersMiddleware(MiddlewareMixin):
"""Middleware to add custom headers to all responses."""

def process_response(self, request, response):
response['X-Custom-Header'] = 'CustomValue'
response['Cache-Control'] = 'no-cache, no-store, must-revalidate'
response['Pragma'] = 'no-cache'
response['Expires'] = '0'
return response

4. Performance Monitoring Middleware

This middleware tracks slow requests and logs them for further investigation:

python
import time
import logging
from django.utils.deprecation import MiddlewareMixin

logger = logging.getLogger('performance')

class PerformanceMonitoringMiddleware(MiddlewareMixin):
"""Middleware to monitor performance of requests."""

THRESHOLD = 1.0 # seconds

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
if duration > self.THRESHOLD:
logger.warning(
f"Slow request: {request.method} {request.path} took {duration:.2f}s"
)
# You could also collect additional context here
# such as query parameters, user info, etc.
return response

Using Middleware for Cross-Cutting Concerns

Middleware is ideal for handling cross-cutting concerns—aspects of your application that affect multiple parts of the system. Here are some examples:

CORS (Cross-Origin Resource Sharing) Middleware

python
from django.utils.deprecation import MiddlewareMixin

class CORSMiddleware(MiddlewareMixin):
"""Middleware to handle CORS headers."""

def process_response(self, request, response):
response['Access-Control-Allow-Origin'] = 'https://example.com'
response['Access-Control-Allow-Methods'] = 'GET, POST, OPTIONS'
response['Access-Control-Allow-Headers'] = 'Content-Type, Authorization'

# Handle preflight OPTIONS requests
if request.method == 'OPTIONS':
response.status_code = 200
return response

return response

Request ID Middleware

This middleware assigns a unique ID to each request, making it easier to track requests across different systems:

python
import uuid
from django.utils.deprecation import MiddlewareMixin

class RequestIDMiddleware(MiddlewareMixin):
"""Middleware to assign a unique ID to each request."""

def process_request(self, request):
request_id = str(uuid.uuid4())
request.id = request_id

def process_response(self, request, response):
if hasattr(request, 'id'):
response['X-Request-ID'] = request.id
return response

Advanced Middleware Concepts

Middleware Ordering

The order of middleware in the MIDDLEWARE list is important. Middleware are processed in the order they're defined for requests, and in reverse order for responses. Consider this carefully when designing multiple middleware that may interact with each other.

Middleware and Django Settings

It's good practice to make your middleware configurable through Django settings:

python
from django.conf import settings
from django.utils.deprecation import MiddlewareMixin

class ConfigurableMiddleware(MiddlewareMixin):
"""Example of a configurable middleware."""

def process_request(self, request):
# Get configuration from settings with defaults
enabled = getattr(settings, 'CUSTOM_MIDDLEWARE_ENABLED', True)
if not enabled:
return None

# Use other settings
debug_mode = getattr(settings, 'CUSTOM_MIDDLEWARE_DEBUG', False)
if debug_mode:
print(f"Processing request to {request.path}")

Class-Based vs. Function-Based Middleware

While we've focused on class-based middleware (the current recommended approach), you can also create function-based middleware:

python
def simple_middleware(get_response):
# One-time configuration and initialization

def middleware(request):
# Code to be executed for each request before the view is called

response = get_response(request)

# Code to be executed for each response after the view is called

return response

return middleware

Add it to your MIDDLEWARE settings just like class-based middleware.

Summary

Custom middleware is a powerful feature in Django that allows you to:

  • Process requests and responses globally
  • Implement cross-cutting concerns across your application
  • Add functionality that applies to all views
  • Keep your view code clean by separating concerns

By creating custom middleware, you can enhance your Django application with reusable components that handle common tasks such as authentication, logging, performance monitoring, and response modification.

Remember these key points:

  1. Middleware processes requests in order (top to bottom) and responses in reverse order
  2. Middleware can short-circuit the request process by returning a response
  3. Consider the order of middleware carefully when multiple middleware interact
  4. Use settings to make your middleware configurable

Exercises

  1. Create a middleware that adds a random quote to the bottom of HTML responses
  2. Implement a middleware that blocks requests based on a rate limit (e.g., no more than 100 requests per minute from a single IP)
  3. Develop a middleware that logs all POST data (excluding passwords and sensitive information)
  4. Create a middleware that adds timing information to the response headers

Additional Resources



If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)