Skip to main content

Django Middleware Introduction

In this tutorial, you'll learn about Django middleware — one of Django's most powerful yet often misunderstood features. Middleware provides a way to process requests and responses globally throughout your Django application.

What is Middleware?

Middleware in Django is a lightweight, low-level plugin system for globally altering Django's input or output. Each middleware component is responsible for doing some specific function. For example, Django includes middleware that:

  • Handles session management
  • Authenticates users
  • Secures requests with CSRF protection
  • And much more!

Think of middleware as a series of "filters" that each request or response must pass through before reaching the view or the client.

How Middleware Works

When a client makes a request to a Django application, the request passes through all the middleware listed in your MIDDLEWARE setting (from top to bottom) before reaching the view. Once the view processes the request and returns a response, the response passes back through all the middleware (from bottom to top) before being sent to the client.

Middleware Flow

Request/Response Cycle

  1. Client sends a request to the server
  2. The request passes through all middleware (top to bottom)
  3. The view processes the request
  4. The view returns a response
  5. The response passes through all middleware (bottom to top)
  6. The response is sent back to the client

Default Middleware in Django

Django comes with several built-in middleware components. Here's a look at the default MIDDLEWARE setting:

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',
]

Let's explore what some of these do:

  • SecurityMiddleware: Handles several security enhancements
  • SessionMiddleware: Enables session support
  • CommonMiddleware: Handles common request processing like URL rewriting
  • CsrfViewMiddleware: Adds protection against Cross Site Request Forgeries
  • AuthenticationMiddleware: Associates users with requests using sessions
  • MessageMiddleware: Enables cookie and session-based message support
  • XFrameOptionsMiddleware: Protects against clickjacking via the X-Frame-Options header

Creating Your First Middleware

Now, let's create a simple custom middleware. We'll make one that logs the time it takes to process each request.

Step 1: Define the Middleware Class

Create a new file called middleware.py in one of your Django app directories:

python
import time
import logging

logger = logging.getLogger(__name__)

class RequestTimingMiddleware:
def __init__(self, get_response):
self.get_response = get_response
# One-time configuration and initialization

def __call__(self, request):
# Code to be executed before the view (and other middleware) is called
start_time = time.time()

response = self.get_response(request)

# Code to be executed after the view is called
duration = time.time() - start_time
logger.info(f"Request to {request.path} took {duration:.2f}s")

return response

Step 2: Add the Middleware to Settings

Add your middleware to the MIDDLEWARE list in your 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.RequestTimingMiddleware', # Add your middleware here
]

Now, every request will be timed, and the duration will be logged.

Middleware Hook Methods

Besides the __call__ method, middleware can define several other specialized methods:

process_view(request, view_func, view_args, view_kwargs)

Called just before Django calls the view. Returns either None or an HttpResponse object.

process_exception(request, exception)

Called when a view raises an exception. Returns either None or an HttpResponse object.

process_template_response(request, response)

Called just after the view has finished executing, if the response has a render() method.

Let's update our middleware to use the process_exception hook:

python
class RequestTimingMiddleware:
def __init__(self, get_response):
self.get_response = get_response

def __call__(self, request):
start_time = time.time()
response = self.get_response(request)
duration = time.time() - start_time
logger.info(f"Request to {request.path} took {duration:.2f}s")
return response

def process_exception(self, request, exception):
logger.error(f"Exception occurred while processing {request.path}: {str(exception)}")
return None # We're not handling the exception, just logging it

Real-World Use Case: IP-Based Access Restriction

Let's create a practical middleware that restricts access to certain views based on IP address:

python
class IPRestrictionMiddleware:
def __init__(self, get_response):
self.get_response = get_response
# Define allowed IPs for restricted areas
self.restricted_paths = ['/admin/', '/api/']
self.allowed_ips = ['127.0.0.1', '192.168.1.100']

def __call__(self, request):
# Check if the path is in restricted areas
if any(request.path.startswith(path) for path in self.restricted_paths):
# Get client's IP
ip = self.get_client_ip(request)
if ip not in self.allowed_ips:
from django.http import HttpResponseForbidden
return HttpResponseForbidden("Access denied based on your IP address.")

return self.get_response(request)

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

Add this middleware to your MIDDLEWARE setting, and it will restrict access to the admin and API areas to only the specified IP addresses.

Best Practices for Middleware

  1. Keep it simple: Middleware should do one thing and do it well
  2. Make it efficient: Since middleware runs on every request, keep it lightweight
  3. Order matters: Understand the order of middleware execution
  4. Be careful with exceptions: Properly handle exceptions to avoid breaking the application
  5. Test thoroughly: Test your middleware with different scenarios

Summary

Django middleware is a powerful system that allows you to process requests and responses globally throughout your application. You've learned:

  • What middleware is and how it works
  • The request/response cycle in Django
  • Default middleware included in Django
  • How to create custom middleware
  • Advanced middleware features like hook methods
  • A real-world example of IP-based access restriction

By leveraging middleware, you can add global functionality to your Django application without modifying individual views.

Additional Resources

Exercises

  1. Create a middleware that adds a custom header to every response
  2. Build a middleware that logs all POST requests to a file
  3. Implement a middleware that detects and blocks potential bot traffic based on user agent
  4. Create a middleware that measures database query count and time for each request
  5. Develop a rate-limiting middleware that restricts the number of requests per minute from a single IP

Happy coding with Django middleware!



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