Building modern web applications often requires a backend that exposes data and operations via RESTful APIs. Django REST Framework (DRF) is one of the most powerful and flexible libraries available for building RESTful APIs using Django. One of its core strengths is how easily it allows developers to perform CRUD operations — Create, Read, Update, and Delete — on their models with minimal effort.
This article provides a detailed explanation of how to perform CRUD operations in Django REST Framework, both using ModelViewSet (which automates the process) and through more granular views for customized behavior. You will learn how to create APIs that can handle data seamlessly, how to override methods for more control, and how to test these endpoints.
Table of Contents
- Introduction to CRUD in Django REST Framework
- Setting Up a Django Project
- Creating a Django App
- Defining the Book Model
- Creating a Serializer
- Understanding ViewSets and ModelViewSet
- Performing CRUD Operations Using ModelViewSet
- Customizing CRUD Behavior
- Using Routers to Register ViewSets
- Testing CRUD Endpoints
- Handling Permissions and Authentication
- Handling Validations and Errors
- Custom API Views (Using APIView and Generic Views)
- Partial Updates with PATCH
- Soft Deletes and Custom Delete Logic
- Filtering, Searching, and Ordering
- Conclusion
1. Introduction to CRUD in Django REST Framework
In any data-driven application, CRUD operations form the foundation. CRUD stands for:
- Create – Add a new record to the database.
- Read – Retrieve existing data.
- Update – Modify existing records.
- Delete – Remove records from the system.
The Django REST Framework simplifies these actions dramatically. Instead of writing boilerplate code for every model, DRF provides generic views, mixins, and viewsets that abstract the repetitive parts, allowing developers to focus on business logic.
For example, a single class using ModelViewSet can automatically handle all four CRUD operations for a given model.
2. Setting Up a Django Project
Before you begin, make sure Django and Django REST Framework are installed. You can install them via pip.
pip install django djangorestframework
Now, create a new Django project.
django-admin startproject library_project
cd library_project
Start a new app named books.
python manage.py startapp books
Add both rest_framework and books to your INSTALLED_APPS in library_project/settings.py.
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'rest_framework',
'books',
]
Now, migrate the database:
python manage.py migrate
You now have a functional Django project set up with DRF ready to use.
3. Creating a Django App
Inside the books app, you will create the model, serializer, and views for your CRUD operations. Let’s begin with the model.
4. Defining the Book Model
Open the file books/models.py and define a simple Book model.
from django.db import models
from django.contrib.auth.models import User
class Book(models.Model):
title = models.CharField(max_length=200)
author = models.CharField(max_length=100)
description = models.TextField(blank=True, null=True)
published_date = models.DateField()
created_by = models.ForeignKey(User, on_delete=models.CASCADE, related_name='books')
def __str__(self):
return self.title
This model includes a created_by field linked to Django’s built-in User model, which allows us to associate each book with the user who created it.
Run the following commands to make and apply migrations:
python manage.py makemigrations
python manage.py migrate
5. Creating a Serializer
A serializer in DRF is responsible for converting complex Django model instances into JSON format and vice versa. Create a file named books/serializers.py and add the following code:
from rest_framework import serializers
from .models import Book
class BookSerializer(serializers.ModelSerializer):
class Meta:
model = Book
fields = ['id', 'title', 'author', 'description', 'published_date', 'created_by']
read_only_fields = ['created_by']
Here, the BookSerializer automatically maps model fields to their JSON representation. We mark created_by as read-only because it will be automatically set by the server (not the client).
6. Understanding ViewSets and ModelViewSet
A ViewSet in DRF is a class-based view that provides CRUD functionality automatically. Specifically, ModelViewSet gives you all CRUD operations without writing separate methods.
Here’s a basic example of how it looks:
from rest_framework import viewsets
from .models import Book
from .serializers import BookSerializer
class BookViewSet(viewsets.ModelViewSet):
queryset = Book.objects.all()
serializer_class = BookSerializer
This single class handles all of the following:
- POST – Create a new book
- GET (list) – Retrieve all books
- GET (detail) – Retrieve one specific book
- PUT/PATCH – Update an existing book
- DELETE – Remove a book
That’s the power of ModelViewSet. But let’s go deeper.
7. Performing CRUD Operations Using ModelViewSet
Let’s see how each CRUD operation is performed in DRF.
7.1 Create (POST)
To create a new book, send a POST request to the endpoint (e.g., /api/books/) with JSON data:
{
"title": "The Great Gatsby",
"author": "F. Scott Fitzgerald",
"description": "A classic novel set in the Jazz Age.",
"published_date": "1925-04-10"
}
DRF automatically validates the data and creates the record in the database.
7.2 Read (GET)
- To list all books:
- GET
/api/books/
- GET
- To get a specific book by ID:
- GET
/api/books/1/
- GET
The response is returned in JSON format.
7.3 Update (PUT/PATCH)
- PUT replaces the entire record.
- PATCH updates specific fields.
Example PATCH request to /api/books/1/:
{
"description": "Updated description for the book."
}
7.4 Delete (DELETE)
To remove a record, send a DELETE request to /api/books/1/. DRF handles deletion automatically.
8. Customizing CRUD Behavior
You can easily customize how these operations behave by overriding specific methods such as perform_create, perform_update, or perform_destroy.
For example, let’s automatically assign the current logged-in user as the creator when creating a book.
def perform_create(self, serializer):
serializer.save(created_by=self.request.user)
You can also customize deletion behavior:
def perform_destroy(self, instance):
if instance.created_by != self.request.user:
raise PermissionDenied("You can only delete books you created.")
instance.delete()
These small overrides provide enormous flexibility without rewriting the entire CRUD logic.
9. Using Routers to Register ViewSets
To make your endpoints accessible, register your BookViewSet with DRF’s router.
In books/urls.py:
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from .views import BookViewSet
router = DefaultRouter()
router.register(r'books', BookViewSet, basename='book')
urlpatterns = [
path('', include(router.urls)),
]
Include this in the project’s main urls.py:
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('api/', include('books.urls')),
]
You can now access all CRUD operations at:
http://127.0.0.1:8000/api/books/
10. Testing CRUD Endpoints
Run the Django server:
python manage.py runserver
Visit the URL in your browser — DRF provides a built-in Browsable API where you can perform CRUD operations interactively.
You can also use tools like Postman, cURL, or HTTPie to test the endpoints.
Example:
curl -X GET http://127.0.0.1:8000/api/books/
11. Handling Permissions and Authentication
In real-world applications, you’ll often want to restrict who can perform CRUD operations. For example, only authenticated users should be able to create or edit books.
Add authentication globally in settings.py:
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.IsAuthenticatedOrReadOnly',
]
}
Or define it in the viewset:
from rest_framework.permissions import IsAuthenticatedOrReadOnly
class BookViewSet(viewsets.ModelViewSet):
queryset = Book.objects.all()
serializer_class = BookSerializer
permission_classes = [IsAuthenticatedOrReadOnly]
This ensures that unauthenticated users can only read data, while authenticated users can create, update, or delete.
12. Handling Validations and Errors
You can define custom validation rules in your serializer.
class BookSerializer(serializers.ModelSerializer):
class Meta:
model = Book
fields = '__all__'
def validate_title(self, value):
if 'django' in value.lower():
raise serializers.ValidationError("Book title cannot contain the word 'Django'.")
return value
DRF automatically handles error responses with descriptive messages and proper HTTP status codes.
13. Custom API Views (Using APIView and Generic Views)
Although ModelViewSet covers most CRUD cases, sometimes you need finer control. DRF offers APIView and generic class-based views for that.
Example using APIView:
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
class BookListCreateView(APIView):
def get(self, request):
books = Book.objects.all()
serializer = BookSerializer(books, many=True)
return Response(serializer.data)
def post(self, request):
serializer = BookSerializer(data=request.data)
if serializer.is_valid():
serializer.save(created_by=request.user)
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
This approach gives you full flexibility but requires more code compared to ModelViewSet.
14. Partial Updates with PATCH
The PATCH method allows partial updates. In DRF, both PUT and PATCH are supported by default in ModelViewSet.
Example request:
{
"author": "Updated Author Name"
}
This only updates the author field, leaving other fields untouched.
15. Soft Deletes and Custom Delete Logic
Sometimes you may not want to permanently delete records. Instead, you can perform a soft delete by marking a record as inactive.
Modify your model:
class Book(models.Model):
title = models.CharField(max_length=200)
author = models.CharField(max_length=100)
description = models.TextField(blank=True, null=True)
published_date = models.DateField()
created_by = models.ForeignKey(User, on_delete=models.CASCADE, related_name='books')
is_active = models.BooleanField(default=True)
Override perform_destroy:
def perform_destroy(self, instance):
instance.is_active = False
instance.save()
Now, deleting a book only sets is_active to False.
16. Filtering, Searching, and Ordering
DRF provides powerful tools for filtering and sorting results.
Install and enable the filter backend:
INSTALLED_APPS += ['django_filters']
In views.py:
from rest_framework import filters
class BookViewSet(viewsets.ModelViewSet):
queryset = Book.objects.all()
serializer_class = BookSerializer
filter_backends = [filters.SearchFilter, filters.OrderingFilter]
search_fields = ['title', 'author']
ordering_fields = ['published_date']
You can now use queries like:
/api/books/?search=gatsby
/api/books/?ordering=-published_date
Leave a Reply