Return JSON error payload instead of HTML text in DRF

Redowan Delowar April 13, 2022
Source
At my workplace, we have a large Django monolith that powers the main website and works as the primary REST API server at the same time. We use Django Rest Framework (DRF) to build and serve the API endpoints. This means, whenever there's an error, based on the incoming request header - we've to return different formats of error responses to the website and API users. The default DRF configuration returns a JSON response when the system experiences an HTTP 400 (bad request) error. However, the server returns an HTML error page to the API users whenever HTTP 403 (forbidden), HTTP 404 (not found), or HTTP 500 (internal server error) occurs. This is suboptimal; JSON APIs should never return HTML text whenever something goes wrong. On the other hand, the website needs those error text to appear accordingly. This happens because 403, 404, and 500 are handled by Django's default handlers for those errors and not by DRF's exception handlers. As the DRF doc suggests on [generic error views], overriding the error handlers is one way of solving it. But this will only work if the application is an API-only backend or if you haven't already overridden the error handlers for custom error pages. In our case, we already had to override the default error handlers to display custom error pages on the website. These custom pages would bleed into the API endpoints occasionally when errors occur. So, I thought, if I could handle this in the middleware layer, that'd be cleaner than most of the solutions that I'd seen at that point. Solution To fix the dilemma, I wrote a middleware called JSONErrorMiddleware that returns the expected response based on the content type in the request header. If the header has Content-Type: html/text and it experiences an error, the server returns an appropriate HTML page. On the contrary, if the incoming request header has Content-Type: application/json and the server sees an error, it responds with a JSON error payload instead. Here's how the middleware looks: You'll have to add this middleware to the list of middlewares in the settings.py file: And voila, now the API and non-API errors will be handled differently as expected! Test Here's how you can unit test the behavior of the middleware: Breadcrumbs This workflow has been tested on Django 3.2, 4.0, and DRF 3.13. Further reading - [HTML sometimes returned when Accept: application/json is provided #3362] - [Added generic 500 and 400 JSON error handlers #5904] [generic error views]: https://www.django-rest-framework.org/api-guide/exceptions/#generic-error-views [html sometimes returned when accept: application/json is provided #3362]: https://github.com/encode/django-rest-framework/issues/3362 [added generic 500 and 400 json error handlers #5904]: https://github.com/encode/django-rest-framework/pull/5904

Discussion in the ATmosphere

Loading comments...