CodingBowl

Building Custom Middleware in Django 5

Published on 1 Nov 2025Development
image
Photo by Zulfugar Karimov on Unsplash

Middleware in Django acts as a bridge between the request and response cycle. It lets you process incoming requests before they reach the view and modify outgoing responses before they’re sent to the client. In this guide, you’ll learn what middleware is and how to build a custom one in Django 5 using the latest approach.

Step 1: Understand What Middleware Does

Middleware is a lightweight layer that processes requests and responses globally. For example, it can log user activity, measure request time, or block unwanted IPs. Django automatically applies middleware to every request, so you can define logic that runs before and after each view.

Step 2: Create a New Django App for Middleware

You can place your custom middleware inside any Django app. Developers often create a “core” app to hold project-level utilities like middleware, context processors, and helpers.

Use the following command to create a new app:

python manage.py startapp core

Then, open your settings.py file and add it to the installed apps:

INSTALLED_APPS = [
    # default Django apps ...
    'core',
]

This ensures Django recognizes your new app and loads its components, including middleware.

Step 3: Write the Middleware Class

In Django 5, middleware follows a modern structure using __init__ and __call__ methods. Here’s an example:

import time
import logging
from django.http import HttpResponseForbidden

logger = logging.getLogger(__name__)

class SecurityAndMetricsMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response
        self.blocked_ips = {"192.168.1.10", "10.0.0.5"}

    def __call__(self, request):
        ip = request.META.get("REMOTE_ADDR", "unknown")
        start_time = time.time()

        if ip in self.blocked_ips:
            return HttpResponseForbidden("Access denied.")

        response = self.get_response(request)
        duration = (time.time() - start_time) * 1000
        response["X-Request-Duration-ms"] = f"{duration:.2f}"
        logger.info(f"{request.method} {request.path} took {duration:.2f} ms")
        return response

This middleware measures how long each request takes and blocks specific IP addresses.

Step 4: Register the Middleware

Open your project’s settings.py file and add your middleware class to the MIDDLEWARE list:

MIDDLEWARE = [
    "django.middleware.security.SecurityMiddleware",
    "django.contrib.sessions.middleware.SessionMiddleware",
    "django.middleware.common.CommonMiddleware",
    "core.middleware.SecurityAndMetricsMiddleware",
]

The order matters—Django processes middleware top to bottom on the way in, and bottom to top on the way out.

Step 5: Test Your Middleware

Run your Django development server with:

python manage.py runserver

Make a few requests to your site. You’ll see timing information printed in your terminal logs and a new response header called X-Request-Duration-ms. If you try accessing the app from a blocked IP, Django will return a “403 Forbidden” response.

Conclusion

Custom middleware is a powerful feature in Django that gives you full control over the request and response cycle. By following these five steps, you can easily extend your project for logging, performance tracking, and security—using Django 5’s modern middleware design.

Meow! AI Assistance Note

This post was created with the assistance of Gemini AI and ChatGPT.
It is shared for informational purposes only and is not intended to mislead, cause harm, or misrepresent facts. While efforts have been made to ensure accuracy, readers are encouraged to verify information independently. Portions of the content may not be entirely original.

image
Photo by Yibo Wei on Unsplash