Django REST Framework
Installing DRF, ModelSerializer, and a complete ViewSet + router example for a JSON API.
What Django REST Framework adds
Django's own views and forms are built for producing HTML. Django REST Framework (DRF) sits on top of the same models, ORM, and URL system and exists specifically for building JSON APIs — serialization, request parsing, authentication/permission hooks, browsable documentation, and class-based views tuned for REST-shaped resources (list, retrieve, create, update, destroy), instead of you assembling all of that by hand out of JsonResponse and manual validation.
It's the de facto standard for building an API on top of Django — if a Django project needs to expose any kind of JSON API, DRF is almost always the first tool reached for rather than a hand-rolled alternative.
Installing DRF
pip install djangorestframework
# mysite/settings.py
INSTALLED_APPS = [
# ...
"rest_framework",
"blog",
]
No migrations are needed just to install DRF itself — it layers on top of models you've already defined and migrated (like the Author and Post models from earlier in this track).
Serializers
A serializer is DRF's equivalent of a Django ModelForm for APIs: it describes how to convert a model instance to a JSON-compatible structure, and how to validate incoming JSON and turn it back into a model instance.
# blog/serializers.py
from rest_framework import serializers
from .models import Author, Post
class PostSerializer(serializers.ModelSerializer):
author_name = serializers.CharField(source="author.name", read_only=True)
class Meta:
model = Post
fields = ["id", "title", "body", "published", "created_at", "author", "author_name"]
read_only_fields = ["id", "created_at"]
ModelSerializer derives its fields directly from the model, the same way ModelForm does — fields controls which of them the API exposes. source="author.name" lets a serializer field reach across a relationship and expose a derived, read-only value (the author's name) that isn't itself a column on Post.
serializer = PostSerializer(post_instance)
serializer.data
# {'id': 1, 'title': 'Hello, Django', 'body': '...', 'published': True, 'created_at': '...', 'author': 1, 'author_name': 'Ada Lovelace'}
A complete ViewSet + router example
A ViewSet groups the standard list/retrieve/create/update/destroy behavior for one resource into a single class, instead of five separate views:
# blog/views.py
from rest_framework import viewsets, permissions
from .models import Post
from .serializers import PostSerializer
class PostViewSet(viewsets.ModelViewSet):
queryset = Post.objects.all().order_by("-created_at")
serializer_class = PostSerializer
permission_classes = [permissions.IsAuthenticatedOrReadOnly]
def get_queryset(self):
if self.request.query_params.get("published") == "true":
return self.queryset.filter(published=True)
return self.queryset
ModelViewSet gives you list, retrieve, create, update, partial_update, and destroy for free from just queryset and serializer_class — get_queryset() is overridden here to support an optional ?published=true filter, exactly the kind of one-method customization class-based views are good at.
A router then generates the full set of URL patterns for a ViewSet automatically:
# blog/urls.py
from rest_framework.routers import DefaultRouter
from .views import PostViewSet
router = DefaultRouter()
router.register("posts", PostViewSet, basename="post")
urlpatterns = router.urls
# mysite/urls.py
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path("admin/", admin.site.urls),
path("api/", include("blog.urls")),
]
router.register("posts", PostViewSet, basename="post") wires up GET/POST /api/posts/ and GET/PUT/PATCH/DELETE /api/posts/{id}/ in one line — the same set of conventional endpoints Route::resource() generates in Laravel, or a ListView+DetailView pair would in plain Django.
The browsable API
Visiting /api/posts/ in a browser (rather than calling it from curl or a JS client) shows DRF's browsable API — an auto-generated HTML interface listing the resource, letting you submit a form to POST a new one, and showing exactly what headers and status code came back. It's enormously useful for manual testing and for exploring an API during development, and it needs zero extra code — it comes from the same serializer and viewset already written above.
Common mistakes
- Writing a plain
serializers.Serializerwith every field spelled out by hand when aModelSerializerwould derive them automatically from the model — extra code to keep in sync every time the model changes. - Forgetting
permission_classes, leaving a viewset's default (oftenAllowAny, depending on project-wideDEFAULT_PERMISSION_CLASSES) wide open to writes from anyone. - Registering a ViewSet directly with
path()instead of a router — it works for narrow cases via.as_view({'get': 'list'}), but throws away the automatic, conventional URL generation a router provides for the full set of actions.
Interview questions
Q: What does a DRF ModelSerializer actually do, and how does it compare to a Django ModelForm?
It derives a set of fields, validation, and (de)serialization logic directly from a model, the same way ModelForm derives form fields from a model — the difference is a ModelSerializer converts between a model instance and JSON-compatible Python data (for an API), while ModelForm converts between a model instance and HTML form fields (for a server-rendered page). Both remove the need to hand-write field definitions that just repeat what the model already declares.
Q: What's the benefit of a ModelViewSet plus a router over writing individual APIView classes for each endpoint?
A ModelViewSet bundles list/retrieve/create/update/destroy behavior for one resource into a single class derived from just queryset and serializer_class, and a router (like DefaultRouter) generates the full, conventional set of URLs for it automatically — GET/POST /posts/ and GET/PUT/PATCH/DELETE /posts/{id}/ — in one router.register() call. Writing separate APIView classes for each of those would mean repeating the same list/detail logic and manually wiring up each URL by hand.