{
"$type": "site.standard.document",
"canonicalUrl": "https://rednafi.com/python/return-json-error-payload-in-drf/",
"description": "Fix Django REST Framework to return JSON error responses for 403, 404, 500 errors using middleware instead of default HTML pages.",
"path": "/python/return-json-error-payload-in-drf/",
"publishedAt": "2022-04-13T00:00:00.000Z",
"site": "at://did:plc:fgtm2c26vfcj74rfmeggbyqj/site.standard.publication/3mnl6f7ob462z",
"tags": [
"Python",
"API",
"Django"
],
"textContent": "At my workplace, we have a large Django monolith that powers the main website and works as\nthe primary REST API server at the same time. We use Django Rest Framework (DRF) to build\nand serve the API endpoints. This means, whenever there's an error, based on the incoming\nrequest header - we've to return different formats of error responses to the website and API\nusers.\n\nThe default DRF configuration returns a JSON response when the system experiences an HTTP\n400 (bad request) error. However, the server returns an HTML error page to the API users\nwhenever HTTP 403 (forbidden), HTTP 404 (not found), or HTTP 500 (internal server error)\noccurs. This is suboptimal; JSON APIs should never return HTML text whenever something goes\nwrong. On the other hand, the website needs those error text to appear accordingly.\n\nThis happens because 403, 404, and 500 are handled by Django's default handlers for those\nerrors and not by DRF's exception handlers. As the DRF doc suggests on [generic error\nviews], overriding the error handlers is one way of solving it. But this will only work if\nthe application is an API-only backend or if you haven't already overridden the error\nhandlers for custom error pages.\n\nIn our case, we already had to override the default error handlers to display custom error\npages on the website. These custom pages would bleed into the API endpoints occasionally\nwhen errors occur. So, I thought, if I could handle this in the middleware layer, that'd be\ncleaner than most of the solutions that I'd seen at that point.\n\nSolution\n\nTo fix the dilemma, I wrote a middleware called JSONErrorMiddleware that returns the\nexpected response based on the content type in the request header. If the header has\nContent-Type: html/text and it experiences an error, the server returns an appropriate\nHTML page. On the contrary, if the incoming request header has\nContent-Type: application/json and the server sees an error, it responds with a JSON error\npayload instead. Here's how the middleware looks:\n\nYou'll have to add this middleware to the list of middlewares in the settings.py file:\n\nAnd voila, now the API and non-API errors will be handled differently as expected!\n\nTest\n\nHere's how you can unit test the behavior of the middleware:\n\nBreadcrumbs\n\nThis workflow has been tested on Django 3.2, 4.0, and DRF 3.13.\n\nFurther reading\n\n- [HTML sometimes returned when Accept: application/json is provided #3362]\n- [Added generic 500 and 400 JSON error handlers #5904]\n\n\n\n\n[generic error views]:\n https://www.django-rest-framework.org/api-guide/exceptions/#generic-error-views\n\n[html sometimes returned when accept: application/json is provided #3362]:\n https://github.com/encode/django-rest-framework/issues/3362\n\n[added generic 500 and 400 json error handlers #5904]:\n https://github.com/encode/django-rest-framework/pull/5904",
"title": "Return JSON error payload instead of HTML text in DRF"
}