Standardizing API Responses in Django REST Framework: A Step-by-Step Guide

1. Introduction When building APIs in Django, having a structured response format improves consistency and maintainability. In this post, we’ll set up a simple API response template to standardize success and error responses in Django REST framework. 2. The Problem First, we create a new Django project and install Django REST framework. See the demo code: python3 -m django startproject api_template_response cd api_template_response python3 -m venv env source env/bin/activate pip install djangorestframework Add rest_framework to your INSTALLED_APPS setting: INSTALLED_APPS = [ ... 'rest_framework', ] Example User API Response Consider the following Django API view for fetching user data: class UserSerializer(serializers.HyperlinkedModelSerializer): class Meta: model = User fields = ['url', 'username', 'email', 'is_staff'] class UserAPIView(views.APIView): def get(self, request): users = User.objects.all() serializer = UserSerializer(users, many=True, context={'request': request}) return Response(serializer.data) This API returns: { "url": "http://127.0.0.1:8000/api/users/1/", "username": "admin", "email": "", "is_staff": true } However, many projects prefer using a standardized API response template, such as: { "success": true, "version": "1.0.0", "data": { "url": "http://127.0.0.1:8000/api/users/1/", "username": "admin", "email": "", "is_staff": true } } 3. API Response Template To achieve this, we create a helper function for consistent API responses: def handle_200_response(data): response_data = { "version": "1.0.0", "status": True, "data": data, } return Response(response_data, status=200) Using this function ensures all success responses follow the same format. However, if you are using Django ViewSets, manually overriding every function (list, create, destroy, etc.) can become complex. 4. Applying the Template to All APIs Django REST Framework provides custom renderers that allow us to modify API responses globally. Let's create a custom renderer: # api_template_response/renderers.py from rest_framework.renderers import JSONRenderer from rest_framework import status class CoreRenderer(JSONRenderer): def render(self, data, accepted_media_type=None, renderer_context=None): status_code = renderer_context["response"].status_code is_success = status.is_success(status_code) response_data = { "status": is_success, "version": "1.0.0", } if is_success: response_data["data"] = data else: response_data["error"] = data return super().render(response_data, accepted_media_type, renderer_context) Then, configure Django REST Framework to use this renderer globally in settings.py: REST_FRAMEWORK = { 'DEFAULT_RENDERER_CLASSES': [ 'api_template_response.renderers.CoreRenderer' ] } Testing with JWT Authentication Now, let's add JWT authentication by installing djangorestframework-simplejwt: pip install djangorestframework-simplejwt Then, configure the authentication endpoints in urls.py: from rest_framework_simplejwt.views import TokenObtainPairView, TokenRefreshView urlpatterns = [ ... path('api/token/', TokenObtainPairView.as_view(), name='token_obtain_pair'), path('api/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'), ] Example API Responses Failed Token Request curl --location --request POST 'http://127.0.0.1:8000/api/token/' Response: { "status": false, "version": "1.0.0", "error": { "username": ["This field is required."], "password": ["This field is required."] } } Successful Token Request curl --location --request POST 'http://127.0.0.1:8000/api/token/' \ --header 'Content-Type: application/json' \ --data-raw '{ "username": "admin", "password": "admin" }' Response: { "status": true, "version": "1.0.0", "data": { "refresh": "", "access": "" } } Adding Authentication to the ViewSet from rest_framework.permissions import IsAuthenticated class UserViewSet(viewsets.ModelViewSet): permission_classes = [IsAuthenticated] queryset = User.objects.all() serializer_class = UserSerializer If authentication fails, the response will be: { "status": false, "version": "1.0.0", "error": { "detail": "Authentication credentials were not provided." } } If successful: { "status": true, "version": "1.0.0", "data": { "url": "http://127.0.0.1:8000/api/users/1/", "username": "admin", "email": "", "is_staff": true } }

Mar 15, 2025 - 17:23
 0
Standardizing API Responses in Django REST Framework: A Step-by-Step Guide

1. Introduction

When building APIs in Django, having a structured response format improves consistency and maintainability. In this post, we’ll set up a simple API response template to standardize success and error responses in Django REST framework.

2. The Problem

First, we create a new Django project and install Django REST framework. See the demo code:

python3 -m django startproject api_template_response
cd api_template_response
python3 -m venv env
source env/bin/activate
pip install djangorestframework

Add rest_framework to your INSTALLED_APPS setting:

INSTALLED_APPS = [
    ...
    'rest_framework',
]

Example User API Response

Consider the following Django API view for fetching user data:

class UserSerializer(serializers.HyperlinkedModelSerializer):
    class Meta:
        model = User
        fields = ['url', 'username', 'email', 'is_staff']

class UserAPIView(views.APIView):
    def get(self, request):
        users = User.objects.all()
        serializer = UserSerializer(users, many=True, context={'request': request})
        return Response(serializer.data)

This API returns:

{
    "url": "http://127.0.0.1:8000/api/users/1/",
    "username": "admin",
    "email": "",
    "is_staff": true
}

However, many projects prefer using a standardized API response template, such as:

{
    "success": true,
    "version": "1.0.0",
    "data": {
        "url": "http://127.0.0.1:8000/api/users/1/",
        "username": "admin",
        "email": "",
        "is_staff": true
    }
}

3. API Response Template

To achieve this, we create a helper function for consistent API responses:

def handle_200_response(data):
    response_data = {
        "version": "1.0.0",
        "status": True,
        "data": data,
    }
    return Response(response_data, status=200)

Using this function ensures all success responses follow the same format. However, if you are using Django ViewSets, manually overriding every function (list, create, destroy, etc.) can become complex.

4. Applying the Template to All APIs

Django REST Framework provides custom renderers that allow us to modify API responses globally. Let's create a custom renderer:

# api_template_response/renderers.py
from rest_framework.renderers import JSONRenderer
from rest_framework import status

class CoreRenderer(JSONRenderer):
    def render(self, data, accepted_media_type=None, renderer_context=None):
        status_code = renderer_context["response"].status_code
        is_success = status.is_success(status_code)

        response_data = {
            "status": is_success,
            "version": "1.0.0",
        }

        if is_success:
            response_data["data"] = data
        else:
            response_data["error"] = data

        return super().render(response_data, accepted_media_type, renderer_context)

Then, configure Django REST Framework to use this renderer globally in settings.py:

REST_FRAMEWORK = {
    'DEFAULT_RENDERER_CLASSES': [
        'api_template_response.renderers.CoreRenderer'
    ]
}

Testing with JWT Authentication

Now, let's add JWT authentication by installing djangorestframework-simplejwt:

pip install djangorestframework-simplejwt

Then, configure the authentication endpoints in urls.py:

from rest_framework_simplejwt.views import TokenObtainPairView, TokenRefreshView

urlpatterns = [
    ...
    path('api/token/', TokenObtainPairView.as_view(), name='token_obtain_pair'),
    path('api/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'),
]

Example API Responses

Failed Token Request

curl --location --request POST 'http://127.0.0.1:8000/api/token/'

Response:

{
    "status": false,
    "version": "1.0.0",
    "error": {
        "username": ["This field is required."],
        "password": ["This field is required."]
    }
}

Successful Token Request

curl --location --request POST 'http://127.0.0.1:8000/api/token/' \
--header 'Content-Type: application/json' \
--data-raw '{
    "username": "admin",
    "password": "admin"
}'

Response:

{
    "status": true,
    "version": "1.0.0",
    "data": {
        "refresh": "",
        "access": ""
    }
}

Adding Authentication to the ViewSet

from rest_framework.permissions import IsAuthenticated

class UserViewSet(viewsets.ModelViewSet):
    permission_classes = [IsAuthenticated]
    queryset = User.objects.all()
    serializer_class = UserSerializer

If authentication fails, the response will be:

{
    "status": false,
    "version": "1.0.0",
    "error": {
        "detail": "Authentication credentials were not provided."
    }
}

If successful:

{
    "status": true,
    "version": "1.0.0",
    "data": {
        "url": "http://127.0.0.1:8000/api/users/1/",
        "username": "admin",
        "email": "",
        "is_staff": true
    }
}

Conclusion

Using a structured API response template enhances consistency, simplifies debugging, and improves API usability. Implement this approach in your Django project to maintain clean and predictable API responses.

See the demo code.