Django REST Framework: Building a CRUD API with Serializers and ViewSets

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 models

class 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 serializers
from .models import Task

class 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 ModelViewSet
from .models import Task
from .serializers import TaskSerializer

class 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 DefaultRouter
from .views import TaskViewSet

router = 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.

Comments

No comments yet. Why don’t you start the discussion?

Leave a Reply

Your email address will not be published. Required fields are marked *