backend / web frameworks / django / 07_middlewares.md

Django Middlewares: A Comprehensive Guide

3 interview angles 10 min read source

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

  1. Request Phase: Middlewares process the request in the order they’re defined
  2. View Execution: The view function/class is executed
  3. 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_request is called before the view is executed and can modify the request or return a response to short-circuit the process
  • process_response is 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:

  1. Security: Authentication, CSRF protection, security headers
  2. Performance: Caching, compression, monitoring
  3. Functionality: Logging, rate limiting, custom headers
  4. 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.