The TLDR answer:

  • Create a file custom_middleware.py file in the same folder as where Django settings.py file is present
  • Add the following code in custom_middleware.py
class CustomMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):

	print("custom middleware before next middleware/view")
        # Code to be executed for each request before
        # the view (and later middleware) are called.

        
        response = self.get_response(request)

	# Code to be executed for each response after the view is called
        # 
        print("custom middleware after response or previous middleware")
        
        return response
custom_middleware.py
  • Add the custom middleware in your 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",

    "custom_middleware.CustomMiddleware", # add custom middleware
    
]

....
settings.py

That's it. You created a custom middleware that will print whenever a request is sent to your application and also when the response is sent back

The Detail answer:

Django Middleware is a regular Python class that hooks into Django's request/response cycle. Generally, Django middleware is a plug-and-play system that can be used to modify a request before the Django view processes it or alter the response which is sent back from Django view to the client.

Django middleware classes are called when the request is sent from the client and the next time when the response is sent to the client. By default, Django gives certain middleware classes out of the box. Most of them are security-related, you can find this middleware in the settings.py file.

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",
]
default middleware classes

Django middleware classes are executed layer by layer, when a request comes then it executes Top to Bottom, layer by layer middleware classes, once the request is processed by the view and response is returned, then it would execute every middleware class opposite order, i.e. from bottom to top. The following image depicts the request-response cycle and how Django middleware is executed.

Django middleware concept

Django middleware makes use of the simple python class concept. Once the middleware class is instantiated then for every request it would execute all the code written inside the __call__ method.

The get_response method call acts like the yield the keyword which would then pass the control to the next middleware or the Django view, and once the response is returned, then it would resume the execution from the point where get_response was left.

Now let us create a custom middleware, in the file custom_middleware.py add the following code.

class CustomMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):

	print("custom middleware before next middleware/view")
        # Code to be executed for each request before
        # the view (and later middleware) are called.

        
        response = self.get_response(request)

	# Code to be executed for each response after the view is called
        print("custom middleware after response or previous middleware")
        
        return response
custom_middleware.py
Once you have written the middleware code, you would have to plug it in to your Django request/response flow. You need to add the entry to setting.py file MIDDLEWARE section. Please remember to add the middleware at an appropriate position, since the evaluation of middleware is position dependent.

Let's look into how this code actually works

  • When you start your Django server, then Django will go and pick up all the middleware you have mentioned in the settings.py file and instantiate it.
  • CustomMiddleware gets instantiated and the __init__ constructor is called.
  • Whenever a request would come to Django then, Django would then start calling the middleware one by one from top-down list mentioned in the settings.py
  • __call__ the method gets called, for the first middleware and subsequent middleware which are built-in or custom-built are called.
  • Once the request reaches CustomMiddleware then the __call__ the method is called which executes the following code.
  • print("custom middleware before next middleware/view") would get executed whenever this middleware is called
  • Once the program reaches response = self.get_response(request) then this would send the request to the next middleware or the Django view.
  • Once the request reaches the view and it would execute and process the request to create a relevant response. The response is then returned, and it would be saved in response a variable
  • The code now starts to execute the next line i.e. print("custom middleware after response or previous middleware")
  • At this point we have access to the response object, if we want to tweak something in the response object then we can do it now.
  • Finally, we return the response to the previous middleware or the client.

When do we use custom middleware?

Custom middleware should be used when you want to implement certain code for every request or response or both. Most of the middleware which comes out of the box is sufficient for you to learn Django, I would recommend writing Django Custom middleware only when you have unique requirements in production since it would mean additional overhead to the request-response cycle which certain times can put negative effect.

I have written custom middleware in Django for the following use cases.

  • Multi-tenant architecture using Django where I wanted to set tenant ID for every request
  • Add logging or monitoring to endpoints which you have built. For example, if I wanted to ignore certain endpoints from getting tracked by NewRelic in Django, the best way to do so is using middleware.
How to ignore certain endpoints in NewRelic APM in Django?
The best way to ignore certain endpoints/api from NewRelic APM would be to tweakthe NewRelic agent flag using a middleware from newrelic import agent class CustomMiddleware: def __init__(self, get_response): self.get_response = get_response def __call__(self, request): #…