Advanced API Concept in Django Rest Framework

Advanced API Concept in Django Rest Framework

This article will discuss some API concepts in Django Rest Framework that make our code shorter and build APIs in Python Django Rest Framework much faster. Below are a few of those concepts:

- Class-based Views (CBVs)
- Generic Views (MIxins and Customization)
- ViewSets
- Routers and Nested Routes

We will explain these concepts with code snippets and build a simple Reviews API that uses these concepts together.

Class Based Views

This concept allows you to move from the usual function-based view to a more convenient and aligned way of writing your Views with inbuilt functions available in the Django rest framework itself

# Class Based View for review api 

@api_view(['GET', 'POST'])
def review_list(request):
    if request.method == 'GET':
        reviews = Review.objects.all()
        serializer = ReviewSerializer(reviews, many=True)
        return Response(serializer.data)
    elif request.method == 'POST':
        serializer = ReviewSerializer(data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

The code snippet above is a typical function-based concept where you have a view review_list that has GET and POST properties with the annotations to get and also modify the reviews list, now using the CBVs Concept,

from rest_framework.views import APIView
# Class Based View for review api 
class ReviewListAPIView(APIView):
    def get(self, request):
        reviews = Review.objects.all()
        serializer = ReviewSerializer(reviews, many=True)
        return Response(serializer.data)

    def post(self, request):
        serializer = ReviewSerializer(data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

As you can see in the Code with the help of the APIView it is easier to access the inbuilt function in the APIView which demonstrates the CBVs Concept

Generic Views ( Mixins and Customization)

Unlike class-based views with generic views the boilerplate gets shorter, with the concept of generic view you can see the kind of role you want that view to perform, an example of a simple generic view is down below:

# views.py
from rest_framework.generics import ListCreateAPIView
from .models import Reviews
from .serializers import ReviewSerializer

class ReviewsListView(ListCreateAPIView):
     queryset = Reviews.objects.all()
     serializer_class = ReviewSerializer

With the code above, you will see the ListCreateAPIView is a powerful DRF class that handles listing (GET) and creating (POST) objects, while other generic views like RetrieveUpdateDestroyAPIView provide full control over retrieving, updating, and deleting (GET, PUT/PATCH, DELETE).

Customizing this Views

# views.py
from rest_framework.generics import ListCreateAPIView
from .models import Reviews
from .serializers import ReviewSerializer
from rest_framework.pagination import PageNumberPagination

class CustomPagination(PageNumberPagination):
    page_size = 10 

class ReviewsListView(ListCreateAPIView):
    serializer_class = ReviewSerializer
    pagination_class = CustomPagination

    def get_queryset(self):
        return Reviews.objects.filter(rating__gt=3)

In this snippet above you can see that the query set has been modified and also the pagination for this API which would return 10 items per page

Viewsets

Unlike class-based views with generic views the boilerplate gets shorter, with the concept of generic view you can see the kind of role you want that view to perform, an example of a simple generic view is down below:

# views.py
from rest_framework.viewsets import ModelViewSet
from .models import Reviews
from .serializers import ReviewSerializer

class ReviewsViewSet(ModelViewSet):
    queryset = Reviews.objects.all()
    serializer_class = ReviewSerializer

With the code above, you would see the ModelViewSet which ModelViewSet is a powerful DRF class that inherits from GenericViewSet . It combines multiple mixins—Create, Retrieve, Update, Delete, and List—to automate CRUD operations with minimal code.

Customizing this Viewset

class ReviewsViewSet(ModelViewSet):
    serializer_class = ReviewSerializer

    def get_queryset(self):
        """Return only reviews with a rating above 3"""
        return Reviews.objects.filter(rating__gt=3)

In this snippet above you can see that the query set has been modified to get only Reviews with rating of 3 and above which can help business logic along the line for that product that is if its tied to a certain product which can be any sort of relationship .

Routers and Nested Routes

Registering routes for function based view

Understanding how views works for both function based views and class-based views, it is important to know that these views would need to be registered in the routes section to be easily accessed by DRF for use as endpoints for the APIs, in the code snippet below we would be talking about registering your routes for a function based view:

from django.urls import path
from .views import product_list, product_reviews, product_review_detail

urlpatterns = [
    path('products/', product_list, name='product-list'),
    path('products/<int:product_id>/reviews/', product_reviews, name='product-reviews'),
    path('products/<int:product_id>/reviews/<int:review_id>/', product_review_detail, name='product-review-detail'),
]

In the snippet above, The routes are registered manually and act as follows:

  1. GET /products/ → List all products

  2. GET /products/{product_id}/reviews/ → List reviews for a specific product

  3. POST /products/{product_id}/reviews/ → Create a review for a product

  4. GET /products/{product_id}/reviews/{review_id}/ → Retrieve a specific review

Pros and Cons of using this method

Pros:

1️⃣ Fine-grained control – You have full control over request handling, making customization easier.
2️⃣ Lightweight and simple – Less abstraction than ViewSets, making it easier to understand for beginners.
3️⃣ No dependency on DRF routers – You manually define URLs, which provides flexibility in URL structure.

Cons:

1️⃣ More boilerplate code – You need to manually handle request methods (GET, POST, etc.), increasing redundancy.
2️⃣ Less reusable – Unlike ViewSets, you cannot easily reuse logic across multiple views.
3️⃣ Lack of built-in DRF features – Features like pagination, filtering, and permissions require extra implementation.

Registering routes for Class based view

Talking about about routes got much more interesting with classes based views

from rest_framework_nested.routers import DefaultRouter, NestedDefaultRouter
from .views import ProductViewSet, ReviewsViewSet

router = DefaultRouter()
router.register(r'products', ProductViewSet)  # Parent route

# Nested route for product reviews
reviews_router = NestedDefaultRouter(router, r'products', lookup='product')
reviews_router.register(r'reviews', ReviewsViewSet, basename='product-reviews')

urlpatterns = router.urls + reviews_router.urls

This code creates a parent-child API structure where products/ is the main route, and products/{product_id}/reviews/ is a nested route for related reviews. It uses NestedDefaultRouter to automatically generate hierarchical URLs, reducing manual URL handling while keeping the API organized. As each review is a subset of each product.

Testing

As you can see above, which displays a list of reviews for product{1}, with the help of the ModelViewSet, you can get, create and update

With the help of DRF, you can post and create a new review for this product.

Now for accessing each review, lets access the first review by accessing the route /products/1/reviews/1

With the ModelViewSet, you can update and delete this review.

Thanks for reading.