Django Rest Framework (DRF) is a framework for building web APIs using Django, making the process of building reusable APIs very easy and efficient. It exposes Django models and business logic as RESTful services, handling common API tasks like serialization, authentication, permissions, and request/response formatting. Some of the key functionalities of DRF include:
- Serialization: Convert complex data types (e.g., Django models, querysets) to and from JSON, XML, and other formats
- Request & Response handling
- Viewsets & Generic Views: Rapid API development with reusable, class-based views
- Authentication: Built-in support for session, token, JWT (via extensions), OAuth, and custom authentication
- Permissions & Authorization: Fine-grained access control at global, view, or object level
- Pagination: Easy handling of large datasets with customizable pagination styles
- Filtering & Searching: Built-in filtering, search, and ordering support
This post focuses on building APIs and performing serialization using DRF’s built-in classes, gradually moving from low-level views to higher-level abstractions.
The most basic class implemented by DRF is the APIView class. It gives you full control over request handling while still providing DRF features such as authentication and permissions. Let's consider the following sample Django model:
class Book(models.Model):
name = models.CharField(max_length=100)
category = models.ForeignKey(Category, on_delete=models.CASCADE, null=True, blank=True)
Basic GET APIs with APIView
To create APIs for retrieving all books or a single book by ID, we can implement a GET handler like this:
from rest_framework import status
from rest_framework.views import APIView
from rest_framework.response import Response
from django.shortcuts import get_object_or_404
from events.models import Book
class BookView(APIView):
def get(self, request, book_id=None):
if book_id is not None:
book = get_object_or_404(Book, id=book_id)
return Response(self.serialize_response(book))
books = Book.objects.all()
return Response([self.serialize_response(b) for b in books])
def serialize_response(self, book):
return {
"id": book.id,
"name": book.name,
"category": book.category_id,
}
with URL configuration
path("books/<int:book_id>/", BookView.as_view(), name='book-list'),
path("books/", BookView.as_view(), name='book-details')
If book_id is provided, the API returns a single serialized book. Otherwise, it returns a list of all books.
Creating, Updating, and Deleting with APIView
To support creating, updating, and deleting books, we add POST, PUT, and DELETE methods:
from rest_framework import status
from rest_framework.views import APIView
from rest_framework.response import Response
from django.shortcuts import get_object_or_404
from events.models import Book
class BookView(APIView):
def post(self, request):
data = request.data
book = Book.objects.create(
name=data.get("name"),
category_id=data.get("category"),
)
return Response(
self.serialize_response(book),
status=status.HTTP_201_CREATED
)
def put(self, request, book_id):
book = get_object_or_404(Book, id=book_id)
data = request.data
book.name = data.get("name", book.name)
book.category_id = data.get("category", book.category_id)
book.save()
return Response(
self.serialize_response(book),
status=status.HTTP_200_OK
)
def delete(self, request, book_id):
book = get_object_or_404(Book, id=book_id)
book.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
def serialize_response(self, book):
return {
"id": book.id,
"name": book.name,
"category": book.category_id,
}
DRF’s browsable API allows you to navigate to endpoints such as /books/ or /books/1/ and interact with these APIs directly via HTML forms.
Defining a ModelSerializer
Manually serializing data and handling object creation can become repetitive and error-prone. DRF Serializers solve this by converting complex data into JSON-friendly formats and validating incoming request data before saving it.
They act as the bridge between HTTP data and Django models.
from rest_framework import serializers
from events.models import Book
class BookSerializer(serializers.ModelSerializer):
class Meta:
model = Book
fields = ["id", "name", "category"]
Refactored View Using Serializer
class BookSerializerView(APIView):
# GET /books/ or /books/<id>/
def get(self, request, book_id=None):
if book_id is not None:
book = get_object_or_404(Book, id=book_id)
serializer = BookSerializer(book)
return Response(serializer.data)
books = Book.objects.all()
serializer = BookSerializer(books, many=True)
return Response(serializer.data)
# POST /books/
def post(self, request):
serializer = BookSerializer(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
)
# PUT /books/<id>/
def put(self, request, book_id):
book = get_object_or_404(Book, id=book_id)
serializer = BookSerializer(book, data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
return Response(
serializer.errors,
status=status.HTTP_400_BAD_REQUEST
)
# DELETE /books/<id>/
def delete(self, request, book_id):
book = get_object_or_404(Book, id=book_id)
book.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
You will notice that serialize_response() method is not used anymore, which is handled by BookSerializer.
The urls stay the same, we just change BookView to BookSerializerView.
path("books/<int:book_id>/", BookSerializerView.as_view(), name='book-list'),
path("books/", BookSerializerView.as_view(), name='book-details')
GenericAPIView and Mixins
To reduce boilerplate further, DRF provides GenericAPIView along with mixins that implement common CRUD behavior. GenericAPIView adds support for:
- queryset
- serializer_class
- pagination_class
- filter_backends
When combined with mixins, it provides reusable, well-tested implementations of CRUD operations, resulting in cleaner and more maintainable code.
List & Create View
class BookListCreateView(
mixins.ListModelMixin,
mixins.CreateModelMixin,
generics.GenericAPIView
):
queryset = Book.objects.all()
serializer_class = BookSerializer
def get(self, request, *args, **kwargs):
return self.list(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
return self.create(request, *args, **kwargs)
Retrieve, Update & Delete View
class BookDetailView(
mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
mixins.DestroyModelMixin,
generics.GenericAPIView
):
queryset = Book.objects.all()
serializer_class = BookSerializer
lookup_url_kwarg = "book_id"
def get(self, request, *args, **kwargs):
return self.retrieve(request, *args, **kwargs)
def put(self, request, *args, **kwargs):
return self.update(request, *args, **kwargs)
def delete(self, request, *args, **kwargs):
return self.destroy(request, *args, **kwargs)
DRF requires explicit HTTP method definitions (get, post, etc.) so it knows how to dispatch requests. These methods act as a bridge between HTTP verbs and mixin-provided actions. The url configuration is updated as:
path("books/<int:book_id>/", BookDetailView.as_view(), name='book-list'),
path("books/", BookListCreateView.as_view(), name='book-details')