Django REST Framework (DRF) provides serializers, views, authentication integrations, and routing tools for building REST APIs. Its ModelViewSet can implement common create, read, update, and delete operations with relatively little code.
The convenience is valuable, but a production API still needs validation, permissions, pagination, and suitable database behavior.
Define a model
Suppose an application manages tasks:
from django.db import modelsclass Task(models.Model): title = models.CharField(max_length=200) completed = models.BooleanField(default=False) created_at = models.DateTimeField(auto_now_add=True)
Create and apply migrations after adding the model:
python manage.py makemigrations
python manage.py migrate
The database schema should be managed through Django migrations rather than created manually for every deployment.
Create a serializer
A serializer converts model instances to and from representations such as JSON. It also validates incoming data.
from rest_framework import serializersfrom .models import Taskclass TaskSerializer(serializers.ModelSerializer): class Meta: model = Task fields = [ "id", "title", "completed", "created_at", ] read_only_fields = ["id", "created_at"]
Marking generated fields as read-only prevents clients from supplying values that the application should control.
More complex business validation can be added through field validators or the serializer’s validate methods.
Create a ViewSet
A ModelViewSet provides standard CRUD actions:
from rest_framework.viewsets import ModelViewSetfrom .models import Taskfrom .serializers import TaskSerializerclass TaskViewSet(ModelViewSet): queryset = Task.objects.all().order_by("id") serializer_class = TaskSerializer
By itself, this example is suitable only for a simple demonstration. A real application must configure authentication and permissions according to its security requirements.
Register routes
A DRF router can generate common endpoints:
from rest_framework.routers import DefaultRouterfrom .views import TaskViewSetrouter = DefaultRouter()router.register("tasks", TaskViewSet, basename="task")urlpatterns = router.urls
The generated routes typically include listing, creation, retrieval, updating, partial updating, and deletion.
Configure permissions deliberately
Do not assume that a CRUD endpoint is secure merely because it uses a serializer.
For a private API, define appropriate authentication and permission policies. If tasks belong to individual users, the queryset should restrict results to the authenticated owner, and creation should assign ownership using trusted server-side information.
def get_queryset(self): return Task.objects.filter(owner=self.request.user)
This assumes the model has an owner field. The actual implementation should also handle unauthenticated requests and assign ownership during creation.
Add pagination and filtering
Unbounded list endpoints can return too much data. DRF supports configurable pagination, filtering, and ordering. Use these features to provide predictable responses and avoid expensive queries.
Test the API
Test valid creation, invalid data, retrieval, updates, deletion, permission enforcement, and pagination. Include tests that verify users cannot access another user’s records.
Conclusion
DRF’s serializers and ViewSets make CRUD APIs quick to build, while Django’s models and migrations provide a structured persistence layer. Production readiness depends on the surrounding decisions: permissions, ownership, validation, pagination, and meaningful tests.