Performing CRUD Operations in Django REST Framework

Introduction

Every web application that interacts with data must provide a way to Create, Read, Update, and Delete — commonly known as CRUD operations. Whether you’re building a book library, a task management system, or an e-commerce platform, CRUD operations form the backbone of any backend system.

In Django REST Framework (DRF), handling CRUD operations is remarkably straightforward. DRF provides a robust set of tools that automate most of the repetitive logic while maintaining flexibility to customize behaviors when needed.

This guide covers everything you need to know about performing CRUD operations in DRF — from basic setup to advanced customization. You’ll learn how to:

  • Create a Django REST API from scratch
  • Use ModelViewSet to automatically handle CRUD endpoints
  • Override built-in methods for custom logic
  • Understand the HTTP methods behind CRUD
  • Test CRUD operations using the Django admin or an API client like Postman

By the end of this post, you’ll have a deep understanding of how Django REST Framework makes CRUD operations both simple and powerful.

What Are CRUD Operations?

CRUD is an acronym for Create, Read, Update, and Delete, which represent the four fundamental operations performed on any persistent storage (like a database).

OperationDescriptionHTTP Method
CreateAdd new data (e.g., add a book)POST
ReadRetrieve existing data (list or single item)GET
UpdateModify existing dataPUT or PATCH
DeleteRemove dataDELETE

Django REST Framework aligns perfectly with these operations through its class-based views, serializers, and routers.


Step 1: Setting Up Your Django REST Framework Project

Before implementing CRUD operations, you need a Django project with DRF installed.

1. Create a Django Project

django-admin startproject library_project
cd library_project

2. Create an App

python manage.py startapp books

3. Install Django REST Framework

pip install djangorestframework

4. Add to Installed Apps

In your 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 that the setup is ready, let’s define a model for our API.


Step 2: Creating the Book Model

Inside books/models.py, define a simple Book model:

from django.db import models

class Book(models.Model):
title = models.CharField(max_length=200)
author = models.CharField(max_length=100)
description = models.TextField(blank=True)
published_date = models.DateField()
created_by = models.ForeignKey('auth.User', on_delete=models.CASCADE, null=True)
def __str__(self):
    return self.title

This model represents a book with a title, author, description, publication date, and a reference to the user who created it.

Then, run the migrations:

python manage.py makemigrations
python manage.py migrate

Step 3: Creating a Serializer

A serializer converts Django model instances into JSON (for API responses) and also validates and converts JSON data back into model instances (for API requests).

Inside books/serializers.py:

from rest_framework import serializers
from .models import Book

class BookSerializer(serializers.ModelSerializer):
class Meta:
    model = Book
    fields = '__all__'

Using ModelSerializer automatically creates fields based on the Book model, handling validation and serialization for you.


Step 4: Performing CRUD with ModelViewSet

The ModelViewSet class is the most convenient and powerful way to implement CRUD operations in DRF.
It automatically provides list, create, retrieve, update, and destroy actions — all within a single class.

Inside books/views.py:

from rest_framework import viewsets
from .models import Book
from .serializers import BookSerializer

class BookViewSet(viewsets.ModelViewSet):
queryset = Book.objects.all()
serializer_class = BookSerializer

That’s it!
This single class automatically generates all CRUD endpoints for the Book model.


Step 5: Connecting URLs with Routers

Django REST Framework’s routers automatically map viewsets to URL patterns.
In books/urls.py:

from rest_framework.routers import DefaultRouter
from .views import BookViewSet

router = DefaultRouter()
router.register(r'books', BookViewSet, basename='book')

urlpatterns = router.urls

Then, include this in your 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')),
]

Now your API is live at:

http://127.0.0.1:8000/api/books/

Step 6: Testing the CRUD API Endpoints

You can now test your API using Postman, cURL, or the DRF’s built-in API browser.
Let’s review each CRUD operation in detail.


CREATE – Handling POST Requests

Endpoint: /api/books/
Method: POST

Example Request Body

{
  "title": "Django for Beginners",
  "author": "William S. Vincent",
  "description": "An introduction to Django web framework",
  "published_date": "2023-01-01"
}

Example Response

{
  "id": 1,
  "title": "Django for Beginners",
  "author": "William S. Vincent",
  "description": "An introduction to Django web framework",
  "published_date": "2023-01-01",
  "created_by": null
}

DRF automatically validates the input and creates a new record in the database.


READ – Handling GET Requests

1. List All Books

Endpoint: /api/books/
Method: GET

Example Response

[
  {
"id": 1,
"title": "Django for Beginners",
"author": "William S. Vincent",
"description": "An introduction to Django web framework",
"published_date": "2023-01-01",
"created_by": null
}, {
"id": 2,
"title": "Two Scoops of Django",
"author": "Daniel Greenfeld",
"description": "Best practices for Django development",
"published_date": "2022-06-10",
"created_by": null
} ]

2. Retrieve a Single Book

Endpoint: /api/books/1/
Method: GET

Example Response

{
  "id": 1,
  "title": "Django for Beginners",
  "author": "William S. Vincent",
  "description": "An introduction to Django web framework",
  "published_date": "2023-01-01",
  "created_by": null
}

UPDATE – Handling PUT and PATCH Requests

Endpoint: /api/books/1/
Method: PUT or PATCH

Example Request Body (PUT)

{
  "title": "Django for Experts",
  "author": "William S. Vincent",
  "description": "Advanced concepts in Django",
  "published_date": "2024-01-01"
}

Example Response

{
  "id": 1,
  "title": "Django for Experts",
  "author": "William S. Vincent",
  "description": "Advanced concepts in Django",
  "published_date": "2024-01-01",
  "created_by": null
}
  • PUT replaces all fields of a record.
  • PATCH updates only the provided fields.

Example PATCH request:

{
  "description": "Updated book description"
}

DELETE – Handling DELETE Requests

Endpoint: /api/books/1/
Method: DELETE

This removes the record with ID 1 from the database.
You’ll receive an empty response with status code 204 No Content.


Step 7: Overriding Default Behavior

While ModelViewSet handles everything automatically, DRF allows you to override its methods to add custom behavior. For example, you may want to automatically set the user who created the book.

Modify your views.py:

class BookViewSet(viewsets.ModelViewSet):
queryset = Book.objects.all()
serializer_class = BookSerializer
def perform_create(self, serializer):
    serializer.save(created_by=self.request.user)

Now, whenever a user creates a book, the created_by field will be automatically populated.


Step 8: Filtering, Searching, and Ordering

CRUD often involves not just basic creation and reading but also listing data with filters, searches, and sorting. DRF provides built-in support for these.

Enable Filtering and Search

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', 'title']

Example Queries

  • Search by title:
    /api/books/?search=Django
  • Order by published date:
    /api/books/?ordering=published_date

This feature makes your CRUD API far more powerful with minimal effort.


Step 9: Adding Permissions and Authentication

Real-world applications need access control — not everyone should be able to delete or edit data.

In views.py:

from rest_framework.permissions import IsAuthenticatedOrReadOnly

class BookViewSet(viewsets.ModelViewSet):
queryset = Book.objects.all()
serializer_class = BookSerializer
permission_classes = [IsAuthenticatedOrReadOnly]
def perform_create(self, serializer):
    serializer.save(created_by=self.request.user)

This ensures:

  • Authenticated users can create, update, or delete books.
  • Anonymous users can only read (GET).

You can integrate JWT, token-based, or session authentication for secure access.


Step 10: Customizing Responses

Sometimes you may want to modify the default JSON response.
For example, let’s add a success message when a book is created.

from rest_framework.response import Response
from rest_framework import status

class BookViewSet(viewsets.ModelViewSet):
queryset = Book.objects.all()
serializer_class = BookSerializer
def create(self, request, *args, **kwargs):
    serializer = self.get_serializer(data=request.data)
    serializer.is_valid(raise_exception=True)
    self.perform_create(serializer)
    return Response({
        'message': 'Book created successfully!',
        'data': serializer.data
    }, status=status.HTTP_201_CREATED)

Now your POST endpoint returns:

{
  "message": "Book created successfully!",
  "data": {
"id": 1,
"title": "Django for Beginners",
"author": "William S. Vincent",
"description": "An introduction to Django web framework",
"published_date": "2023-01-01"
} }

Step 11: Handling Errors Gracefully

Error handling is crucial for a good API experience.
DRF automatically manages validation errors, but you can customize responses for specific cases.

Example:

def destroy(self, request, *args, **kwargs):
instance = self.get_object()
if instance.created_by != request.user:
    return Response({'error': 'You cannot delete this book'}, status=403)
self.perform_destroy(instance)
return Response({'message': 'Book deleted successfully'}, status=204)

This ensures users can only delete their own books, providing clear messages for unauthorized actions.


Step 12: Extending CRUD with Additional Endpoints

Sometimes, you’ll want to add custom endpoints beyond basic CRUD. DRF’s @action decorator makes this easy.

Example:

from rest_framework.decorators import action
from rest_framework.response import Response

class BookViewSet(viewsets.ModelViewSet):
queryset = Book.objects.all()
serializer_class = BookSerializer
@action(detail=False, methods=['get'])
def recent(self, request):
    recent_books = Book.objects.order_by('-published_date')[:5]
    serializer = self.get_serializer(recent_books, many=True)
    return Response(serializer.data)

Now you can visit:

/api/books/recent/

to get the 5 most recently published books.
This demonstrates how ModelViewSet allows both CRUD and custom endpoints in one class.


Step 13: Testing CRUD Operations with Django’s API Browser

One of DRF’s greatest strengths is its interactive API browser.
When you visit an endpoint like /api/books/ in your browser, DRF automatically provides an HTML interface to test CRUD actions visually.

You can:

  • Create a new book using a form.
  • View lists and details.
  • Edit or delete entries.

This is extremely helpful during development and debugging.


Step 14: Example Project Structure Recap

Here’s how your project structure should look after implementing CRUD:

library_project/
│
├── library_project/
│   ├── settings.py
│   ├── urls.py
│
├── books/
│   ├── models.py
│   ├── serializers.py
│   ├── views.py
│   ├── urls.py
│
└── manage.py

This simple setup supports full CRUD functionality using fewer than 50 lines of actual view code — a testament to DRF’s efficiency.


Step 15: Real-World Use Cases of CRUD in DRF

CRUD operations are at the heart of nearly all REST APIs.
Here are a few real-world examples of how CRUD applies in various systems:

  • E-commerce: Products (Create, List, Update, Delete)
  • Blog: Posts, Comments, Categories
  • User Management: Accounts and Profiles
  • Inventory Management: Items, Suppliers, Orders
  • Education Systems: Courses, Students, Grades

No matter what the application, CRUD endpoints are the foundation of your data management layer.


Step 16: Function-Based Alternative (For Comparison)

If you prefer explicit logic, the same CRUD functionality can be implemented using function-based views.

Example:

from rest_framework.decorators import api_view
from rest_framework.response import Response
from rest_framework import status
from .models import Book
from .serializers import BookSerializer

@api_view(['GET', 'POST'])
def book_list(request):
if request.method == 'GET':
    books = Book.objects.all()
    serializer = BookSerializer(books, many=True)
    return Response(serializer.data)
elif request.method == 'POST':
    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)

While function-based views give more explicit control, ModelViewSet drastically reduces code duplication and scales better.


Step 17: Best Practices for CRUD in Django REST Framework

  1. Use ViewSets Whenever Possible:
    They provide built-in support for all CRUD operations.
  2. Validate Input with Serializers:
    Never manually handle data — serializers ensure integrity.
  3. Secure Your Endpoints:
    Always define permissions and authentication for sensitive operations.
  4. Use Pagination for Large Datasets:
    To avoid overloading responses, enable pagination in settings.py: REST_FRAMEWORK = { 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination', 'PAGE_SIZE': 10 }
  5. Keep Logic in the Right Place:
    Business logic goes in serializers or models, not directly in views.
  6. Use Status Codes Properly:
    Respond with 201 for creation, 204 for deletion, 400 for validation errors, and 403 for permission issues.

Step 18: Advantages of Using ModelViewSet for CRUD

  1. Less Code, More Functionality:
    A few lines of code handle all CRUD actions.
  2. Consistency Across Endpoints:
    The same structure applies across all models.
  3. Integration with Routers:
    URL patterns are automatically generated.
  4. Easy to Extend:
    Add custom actions and override methods without breaking functionality.
  5. Built-in Security and Validation:
    Inherits DRF’s permission and validation mechanisms.

Comments

Leave a Reply

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