Yes, you can build a complete CRUD REST API in Django using class-based views (CBVs) — specifically, the generic ListCreateAPIView and RetrieveUpdateDestroyAPIView from Django REST Framework (DRF). You define a serializer, two view classes, and two URL routes, and you get working list, create, retrieve, update, and delete endpoints with almost no boilerplate. This article explains exactly how that works, when to use it instead of function-based views or viewsets, and what pitfalls to avoid.
Background: Why Class-Based Views for APIs?
Django’s function-based views (FBVs) have been around since the beginning. They are simple: a function that takes a request and returns a response. But for a CRUD API, you quickly find yourself repeating the same logic — fetch an object or return 404, check permissions, serialize, deserialize, save, return the right status code. Class-based views were introduced to package that repetition into reusable pieces. Django REST Framework takes the idea further with generic views that combine a set of common behaviors (mixin classes) into a single class you can subclass.
For example, ListCreateAPIView bundles the ListModelMixin and CreateModelMixin with a generic view. It handles GET (list) and POST (create) on the same URL. RetrieveUpdateDestroyAPIView bundles the retrieve, update, partial update, and delete mixins for a single object URL. You provide queryset and serializer_class, and the framework does the rest.
How It Works: The Two Generic Views You Need
Let’s say you have a Book model with title, author, and published_year. Your API needs to let clients list all books, create a new one, view one book, update it, and delete it. Here is the complete view code:
# views.py
from rest_framework import generics
from .models import Book
from .serializers import BookSerializer
class BookListCreateView(generics.ListCreateAPIView):
queryset = Book.objects.all()
serializer_class = BookSerializer
class BookDetailView(generics.RetrieveUpdateDestroyAPIView):
queryset = Book.objects.all()
serializer_class = BookSerializer
And the serializer:
# serializers.py
from rest_framework import serializers
from .models import Book
class BookSerializer(serializers.ModelSerializer):
class Meta:
model = Book
fields = ['id', 'title', 'author', 'published_year']
Finally, the URL configuration:
# urls.py
from django.urls import path
from .views import BookListCreateView, BookDetailView
urlpatterns = [
path('api/books/', BookListCreateView.as_view(), name='book-list'),
path('api/books/<int:pk>/', BookDetailView.as_view(), name='book-detail'),
]
That’s it. You now have five HTTP operations:
GET /api/books/→ list all booksPOST /api/books/→ create a new bookGET /api/books/1/→ retrieve book with id 1PUT /api/books/1/→ fully update book 1PATCH /api/books/1/→ partially update book 1DELETE /api/books/1/→ delete book 1
The generic views automatically return 404 if the object does not exist, handle form validation errors with 400 responses, and return 201 on successful creation.
Choosing Between Generic CBVs, Viewsets, and Function Views
DRF offers three main ways to write API endpoints. Here is how to decide:
- Generic class-based views — best when you have a simple model and want minimal code. You write two classes per model. This is the sweet spot for most projects.
- Viewsets + ModelViewSet — best when you want to use a router to generate all URL patterns automatically. A single
ModelViewSetclass gives you list, create, retrieve, update, partial update, and delete. You then use aDefaultRouterto register it. This reduces code further but adds a layer of magic; the URL names are generated for you. - Function-based views with @api_view — best for one-off endpoints that don’t fit the generic pattern, or when you need full control over every branch of logic. They are more verbose but easier to trace.
For a standard CRUD API, generic CBVs are the recommended default. They are explicit about what they do, easy to extend with mixins, and easy to debug because each method is a well-known mixin method.
Common Problems and How to Solve Them
Even with generic views, you will run into a few recurring issues:
1. Permission errors
By default, DRF allows any request (unless you set a global default). If you need authentication, add permission_classes to the view. For example, to require a logged-in user for write operations but allow anonymous read:
from rest_framework.permissions import IsAuthenticatedOrReadOnly
class BookListCreateView(generics.ListCreateAPIView):
queryset = Book.objects.all()
serializer_class = BookSerializer
permission_classes = [IsAuthenticatedOrReadOnly]
For per-object permissions (e.g., only the owner can edit), override get_permissions() or use get_object() to check ownership.
2. Nested serializers and write operations
If your serializer includes a nested object (e.g., author is a foreign key to a User model), you need to handle creation/update manually. The default ModelSerializer will not know how to create the nested object. Override create() and update() in the serializer.
3. Filtering and ordering
Generic views do not filter by default. To add query parameters like ?author=3, override get_queryset():
def get_queryset(self):
queryset = Book.objects.all()
author = self.request.query_params.get('author')
if author:
queryset = queryset.filter(author_id=author)
return queryset
For more complex filtering, use django-filter with filter_backends and filterset_fields.
4. Pagination
If you have many records, enable pagination globally in settings or per-view by setting pagination_class. DRF’s PageNumberPagination returns a JSON object with count, next, previous, and results.
Extending the Generic Views: Custom Actions
Sometimes you need a custom action like POST /api/books/1/checkout/. Generic CBVs do not provide a direct way to add a custom method to a detail route. You have two options:
- Add a new URL pattern pointing to a dedicated view (e.g.,
BookCheckoutView) that subclassesAPIViewor uses@actionfrom a viewset. - Switch to a
ModelViewSetand use the@actiondecorator. This is the cleaner approach if you have several custom actions.
For example, with a viewset:
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=True, methods=['post'])
def checkout(self, request, pk=None):
book = self.get_object()
# custom logic
return Response({'status': 'checked out'})
Then the URL is automatically /api/books/1/checkout/ when using a router.
Comparison: Class-Based Views vs. Function-Based Views
Here is a side-by-side for a typical CRUD endpoint:
- Code length: CBVs are 3-5 lines per endpoint; FBVs are 15-20 lines for the same logic.
- Reusability: CBVs can be subclassed and mixed; FBVs require decorators or helper functions.
- Readability: FBVs show every step explicitly; CBVs hide boilerplate but require knowing the mixin methods.
- Testing: Both are equally testable; CBVs are easier to mock because you can call methods directly.
- Learning curve: FBVs are easier for beginners; CBVs take time to learn the method resolution order.
For a production API, CBVs are the standard choice. The only time FBVs make sense is when you have a single endpoint that does something completely custom and doesn’t fit any generic pattern.
Security and Best Practices
When exposing a CRUD API, keep these points in mind:
- Always use
IsAuthenticatedor a custom permission class unless your API is intentionally public. - Validate input with serializer fields — never trust raw request data.
- Use
SELECT_relatedorprefetch_relatedinget_queryset()to avoid N+1 queries. - Set a global
DEFAULT_THROTTLE_RATESto prevent abuse. - Use HTTPS in production and set
SECURE_SSL_REDIRECT. - Do not expose internal model fields in the serializer unless necessary.
Frequently Asked Questions
Do I need to install Django REST Framework separately?
Yes. Django itself does not include REST framework. Install it with pip install djangorestframework and add 'rest_framework' to your INSTALLED_APPS. The generic views we discussed are part of DRF, not core Django.
What is the difference between UpdateAPIView and RetrieveUpdateDestroyAPIView?
UpdateAPIView only handles PUT and PATCH. RetrieveUpdateDestroyAPIView handles GET, PUT, PATCH, and DELETE. Use the latter when you need to both view and modify a single object. If you only need editing, use UpdateAPIView.
How do I return a custom response on create?
Override the create() method in your view. For example:
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({'id': serializer.data['id'], 'message': 'Created!'}, status=201)
Be careful to keep the same status code semantics.
Can I use class-based views with Django’s built-in View class instead of DRF?
Yes, but you would have to manually parse JSON, serialize objects, and return JsonResponse. DRF’s generic views save that work. If you want minimal dependencies, you can use Django’s View with JsonResponse, but you will reimplement validation and serialization.
Why does my POST request return 403 Forbidden?
Most likely because CSRF protection is enabled. DRF’s APIView disables CSRF for session-authenticated requests, but if you are using Django’s View or have a custom authentication, you may need to add @csrf_exempt or use DRF’s SessionAuthentication which handles it. For token authentication, CSRF is not used.
How do I add URL parameters like /api/books/2023/ for year?
Use a URL pattern with a named group, e.g., path('api/books/<int:year>/', BookYearView.as_view()), and in the view override get_queryset() to filter by self.kwargs['year']. This is separate from the detail view; you would need a distinct view class for that.
Related
Method: the standard formula for this calculation, rounded as each result states. See how the calculators work or report an error.
More cards in this drawer
- Channel TLD Websites: What They Are & How to Get One5.00 min/km
- Most Secure OS for Privacy: 3 Picks by Device
- Ryobi One+ Battery System: Is the Switch Worth It?
- Tmux Terminal Upgrade: Is It Worth It?
- Refurbished Tech Buying Checklist
- Highlight Row and Column Intersection Exact Match3