Member preview

RESTful Error Messages with Django

Error messages? Exception handling? Not the sexiest topic, I know, but if you’ve ever found yourself struggling to handle exceptions or validation errors while trying to build a restful API in django, read on.

The Problem

Django, out of the box, does not have a robust mechanism for mapping exceptions to Response objects. There are 4 common ways to trigger http status specific exceptions, the first three can be found in `django.core.exceptions`, which I’ve listed below in order of clarity:

  1. `ViewDoesNotExist` => 404
  2. `PermissionDenied` => 403
  3. `SuspiciousOperation` => 400

The last error is the source of much frustration, the 500 Server Error, which is returned when any generic or custom exception is raised during a request.

Now the problem here is not just that its unclear what http status codes are returned with the above exceptions, the main issue is that none of the errors returned from these exceptions are helpful, and therefore, are not RESTful.

In an ideal RESTful API, all error responses contain detailed messages and explanations for developers to consume. For example, if not including an id param in a GET request causes an IndexError on a list, we don’t want the developer to see a 500 Server Error message. We want to pass along a response body that tells the developer where they went wrong. When an unauthenticated or unauthorized request is made on a resource, we don’t want to return a cryptic block of html with the text “Forbidden”. We want to return a detailed response that informs the developer of any missing keys or tokens that should be included in requests.

So how can we do this? The current “Django way” would be to track the state of the request at each step and return error responses explicitly, when needed. Like so:

from django.http import JsonResponse
def a_view(request):
if foo:
return JsonResponse({'message': "Must login!'}, 401)
if bar:
return JsonResponse({'message': 'This is Forbidden'}, 403)
if baz:
return JsonResponse({'message': 'Resource Not Found'}, 404)

This may be fine for a simple view, but what if your view has multiple internal functions that are called, all of which could produce known errors? The only way to achieve the above would be to litter your code with status tracking tramp data, to be passed along up chains of functions and class methods until they reach the view and are passed into a final Response object.

from django.http import JsonResponse
def do_something(message, status):
...
if bad_data:
message = "Your request contains bad data!"
status = 400
return message, status
def a_view(request):
message = ""
status = 0
    message, status = do_something(message, status)
if status > 200:
return JsonResponse(message, status)

return JsonResponse({'message': "no errors!'}, 200)

This is a simplified example, with just one error case, but you can imagine that as the levels of function call nesting and number of errors to handle increases, this code could grow incredibly unruly and utterly unintelligible.


The Solution, Custom API Error Responses

The fix is rather straightforward, and is actually similar in practice to how Django REST Framework does exception handling. Middleware and registered http exceptions.

customexceptions.py

class BaseCustomException(Exception):
    status_code = None
error_message = None
is_an_error_response = True
    def __init__(self, error_message):
Exception.__init__(self)
self.error_message = error_message
    def to_dict(self):
return {'errorMessage': self.error_message}

class InvalidUsage(BaseCustomException):
status_code = 400

middleware.py

import traceback
from django.http import JsonResponse
def is_registered(exception):
try:
return exception.is_an_error_response
except AttributeError:
return False
class RequestExceptionHandler:

def process_exception(self, request, exception):
if is_registered(exception):
status = exception.status_code
exception_dict = exception.to_dict()
else:
status = 500
exception_dict = {'errorMessage': 'Unexpected Error!'}

error_message = exception_dict['errorMessage']
traceback.print_exc()
return JsonResponse(exception_dict, status=status)

views.py

from django.http import JsonResponse
from customexceptions import InvalidUsage
def do_something():
if bad_data:
raise InvalidUsage("Bad Request! Data is poorly formatted")
def a_view(request):
do_something()
return JsonResponse({'message': "no errors!'}, 200)

Summary

And thats it! If bad_data resolves to True, InvalidUsage will be thrown, triggering our middleware’s process_exception method. This method will check the exception for an `is_an_error_response` attribute, and if one exists it will return a JsonResponse containing the exception message as a dict, and its status code. The only thing we need to do to register new custom exceptions is create subclasses of BaseCustomException, and define the default status code and message! Very slick.

See the django docs for more infor on basic error handling.

Checkout the django rest framework for more inspiration on developing restful APIs, or just to use it (its very powerful!)

For more info on why error handling is so important in building truly RESTful APIs, checkout this article from Apigee.