How to create a common response format for 200, 400, 500 responses by creating custom exception handler in Django Rest Framework?

Published on May 28,2020 by Maulik

In micro-services architecture, multiple client applications are consuming the backend API. The backend server does the core business logic and all heavy lifting. The client application should have to write minimum logic. They should be just displaying the information coming from the backend API. It makes the client application faster and smooth. This article describes best practices for sending customized error messages with customized response formats.

Github Repository

This repository contains the exception handling and implementing custom response formatted in case of 200, 400, or 500 response. It also contains three basic Sign Up, Sign In, and Log out API.

What is Exception?

Program crashing because of logical or programmatical error is an exception occurring in the program. Such exceptions can need handling. If not handling such exception results in the program crashing.

What is Exception Handler in Django?

Django provides rich default exception handling classes. But when we are developing the custom web application, you need your custom exception handling classes. Django provides flexibility to build your custom exception classes.

What is the response?

The response is the HTTP response object returned to the client for it’s HTTP request to the server. In a microservices architecture, client applications can be Angular, Android, or IOS applications. Backend API servers return response in XML or JSON format.

Why we should create a standard response format for 200, 400, 500 responses from the server?

There are always multiple API consumers, and each client application development team will not necessarily follow the same standards. So for implementing a custom solution for handling every kind (structure) of response coming from the server reduces the performance and speed of the client applications.

Default 200 response examples:

"User signup successfully"

Default 400 response examples:

{
    "username": [
            "A user with that username already exists."
        ]
}

Default 500 response examples:

Exception at /users/signup/
Internal server error

Request Method: POST
Request URL: http://localhost:8000/users/signup/
Django Version: 2.2.12

....
Other details of Traceback

Obviously, with such multiple response formats, it becomes a nightmare for front end/client application developers to manage all these responses. It degrades code quality and performance of the client-side applications.

Steps to create custom exception handler and standard response format.

Create exception handler and middleware class

  1. We have to write an exception handler and a middleware class, which handles the exceptions. 
  2. After writing middleware, all the exceptions pass-through from the handlers. 
  3. We format the exceptions and return the formatted response.
  4. Here is the example of the exception handler and the middleware class:
  5. custom_exception_handler.py
  6. from rest_framework.views import exception_handler

    from rest_framework.views import exception_handler
    from django.http import JsonResponse
     
    def get_response(message="", result={}, status=False, status_code=200):
       return {
           "message" : message,
           "result" : result,
           "status" : status,
           "status_code" : status_code,
       }
     
    def get_error_message(error_dict):
       field = next(iter(error_dict))
       response = error_dict[next(iter(error_dict))]
       if isinstance(response, dict):
           response = get_error_message(response)
       elif isinstance(response, list):
           response_message = response[0]
           if isinstance(response_message, dict):
               response = get_error_message(response_message)
           else:
               response = response[0]
       return response
     
    def handle_exception(exc, context):
       error_response = exception_handler(exc, context)
       if error_response is not None:
           error = error_response.data
     
           if isinstance(error, list) and error:
               if isinstance(error[0], dict):
                   error_response.data = get_response(
                       message=get_error_message(error),
                       status_code=error_response.status_code,
                   )
     
               elif isinstance(error[0], str):
                   error_response.data = get_response(
                       message=error[0],
                       status_code=error_response.status_code
                   )
     
           if isinstance(error, dict):
               error_response.data = get_response(
                   message=get_error_message(error),
                   status_code=error_response.status_code
               )
       return error_response
     
    class ExceptionMiddleware(object):
       def __init__(self, get_response):
           self.get_response = get_response
     
       def __call__(self, request):
     
           response = self.get_response(request)
     
           if response.status_code == 500:
               response = get_response(
                   message="Internal server error, please try again later",
                   status_code=response.status_code
               )
               return JsonResponse(response, status=response['status_code'])
     
           if response.status_code == 404 and "Page not found" in str(response.content):
               response = get_response(
                   message="Page not found, invalid url",
                   status_code=response.status_code
               )
               return JsonResponse(response, status=response['status_code'])
     
           return response

     

Configure exception handler in settings

  1. After creating the exception handler and middleware class, require to configure the exception handler and the middleware class in the settings file.
  2. Add an exception handler in the rest framework configuration in settings.py
  3. REST_FRAMEWORK = {
      'DEFAULT_PERMISSION_CLASSES': (
          'rest_framework.permissions.IsAuthenticated',
      ),
      'DEFAULT_AUTHENTICATION_CLASSES': (
          'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
          'rest_framework.authentication.SessionAuthentication',
          'rest_framework.authentication.BasicAuthentication',
      ),
      'EXCEPTION_HANDLER': 'django_exception_handler.custom_exception_handler.handle_exception'
    }

     

  4. Add the middleware class in the middleware list:
  5. MIDDLEWARE = [
    ... other middlewares
    'django_exception_handler.custom_exception_handler.ExceptionMiddleware',
    ]

     

Test the APIViews

  1. By default, on the occurrence of the exception, the response format of the exception is different for different types of the exception. For example, the validation exception raised from the serializer has different formats based on the nesting of the serializers.
  2. If the request URL did not match with any URL, then raised exception returns 404 HTTP page not found an exception.
  3. And if any unhandled condition occurs, then it returns the 500 internal server error.

With our custom exception response implementation, here are response examples of exceptions in requests.

  1. Exception response from the serializers/other raised exceptions.
    • {
         "message": "Username or password is incorrect",
         "result": {},
         "status": false,
         "status_code": 400
      }

       

  2. Exception response when any URL mismatch or not found
    • {
         "message": "Page not found, invalid url",
         "result": {},
         "status": false,
         "status_code": 404
      }

       

  3. Exception response when an unexpected error occurs.
    • {
         "message": "Internal server error, please try again later",
         "result": {},
         "status": false,
         "status_code": 500
      }

       

  4. Exception response for unauthorized request from JWT authentication
    • {
         "message": "Unauthorized user",
         "result": {},
         "status": false,
         "status_code": 403
      }

       

Summary

No matter what is the HTTP response code whether it is 200, 400, or 500 response format will be the same, just the information, message and status code will be variables in response.

2 Comments

Y3E51ZH www.yandex.ru

1 month

Y3E51ZH www.yandex.ru

UxeOF

2 months, 2 weeks

Medicine information. Effects of Drug Abuse. <a href="https://topregabalin.top">lyrica prices</a> in USA Best what you want to know about drugs. Read information here.

copied to clipboard

Sign up for our newsletter

Please join our news letter which we share every month, you would love interesting python and django news letters.

We understand no one like spamming, your emails are safe with us.

Copyright © Django Circle All Rights Reserved.