Exceptions
Exceptions allow error handling to be organized cleanly in a central place within the program structure. In DRF, views automatically catch specific exceptions and convert them into standard HTTP error responses.
Built-in Exception Handling
By default, DRF handles the following exceptions natively inside your views:
- Subclasses of
APIExceptionraised inside DRF. - Django's
Http404exception. - Django's
PermissionDeniedexception.
In each case, DRF intercepts the exception and returns a response with the appropriate status code, content type, and an error body containing details.
Default Error Responses
Most error responses will include a detail key in the JSON body. For example, if a client tries to use a DELETE method where it's not allowed, the response body will look like this:
{
"detail": "Method 'DELETE' not allowed."
}Validation errors (e.g., from serializers) are handled slightly differently. They return the specific field names as keys in the response. If the validation error isn't tied to a specific field, it uses the non_field_errors key (or the value set in your NON_FIELD_ERRORS_KEY setting).
{
"amount": ["A valid integer is required."],
"description": ["This field may not be blank."]
}Custom Exception Handling
While DRF's defaults are great for most applications, you may want to centralize how errors are formatted across your entire API. For instance, you might want every single error response to include the HTTP status code, or you might need to structure errors to match a specific frontend requirement.
By defining a custom exception handler, you keep your code DRY. Instead of writing redundant try/except blocks in every single view, you handle it in one central location.
Writing a Custom Handler
A custom handler function takes two arguments:
exc: The exception being handled.context: A dictionary containing extra context, such as theviewthat raised the error.
The function must return a Response object if it successfully handles the error, or None if it cannot. If it returns None, Django takes over and returns a standard HTTP 500 Server Error.
A common pattern is to call DRF's default exception handler first to get the standard response, and then modify it to suit your needs.
from rest_framework.views import exception_handler
def custom_exception_handler(exc, context):
# Call DRF's default exception handler first
# to get the standard error response.
response = exception_handler(exc, context)
# If the response is not None, an error was caught.
# We can now add extra information, like the status code.
if response is not None:
response.data['status_code'] = response.status_code
return responseNOTE
The context argument is extremely useful if you need to access context['view'] to see exactly which view threw the error, or context['request'] to get request data.
Setting the Custom Handler
Once you've written your handler, you need to tell DRF to use it globally. By default, DRF uses its own built-in rest_framework.views.exception_handler. You can override this in your settings.py:
REST_FRAMEWORK = {
# Default is 'rest_framework.views.exception_handler'
'EXCEPTION_HANDLER': 'my_project.my_app.utils.custom_exception_handler'
}TIP
Custom exception handlers are the perfect place to integrate error monitoring tools (like Sentry) or to unify error response structures so your frontend teams always know exactly what format to expect.
