π Django REST Framework Overview
Django is superb at rendering HTML pages, but modern apps also need to feed mobile apps, single-page frontends, and other services with clean JSON. Django REST Framework (DRF) is the toolkit that turns your Django project into a professional-grade API. This lesson gives you the mental model and your first working endpoints.
π― Learning Objectives
By the end of this lesson, you will be able to:
- Explain what a REST API is and how HTTP verbs map to CRUD operations
- Describe what Django REST Framework adds on top of plain Django and why teams reach for it
- Install and configure DRF in a Django 5 project via
settings.py - Build a first CRUD API three ways β function-based, class-based (APIView), and generic views
- Use the browsable API to test endpoints without any external tool
Estimated Time: 35β45 minutes β’ Difficulty: Intermediate
Hands-on: Build a complete Book API and exercise every endpoint in the browsable API.
In This Lesson
What Is Django REST Framework?
Django REST Framework (universally shortened to DRF) is a mature third-party package that extends Django with everything you need to build, test, and document Web APIs. It has become the de facto standard for APIs in the Django world and powers services at Mozilla, Red Hat, and Eventbrite, among many others.
π‘ The restaurant that opened a delivery kitchen. Plain Django is a restaurant that plates beautiful meals (HTML pages) for diners in the room. DRF converts that kitchen into a food-prep facility that can also package meals for delivery apps (mobile clients), supply ingredients to other kitchens (third-party services), and ship meal kits for home assembly (React/Vue frontends) β while still serving in-house diners when needed. Same kitchen, far greater reach.
Concretely, where a normal Django view returns an HTML template, a DRF view returns JSON (or XML, or another negotiated format) that any client β a phone, a browser app, a smart TV β can consume.
REST and APIs in a Nutshell
Before DRF makes sense, you need the idea it is built around. REST (Representational State Transfer) is an architectural style for networked applications. A RESTful API treats every piece of data as a resource identified by a URL, and uses standard HTTP methods to act on it.
π Key Terms
Resource: a "thing" your API exposes β a book, a user, an order β addressed by a URL such as /api/books/1/.
CRUD: the four basic operations on data β Create, Read, Update, Delete.
Endpoint: a specific URL + method combination the client can call, e.g. GET /api/books/.
The elegance of REST is that HTTP already gives you a verb for each CRUD operation, so URLs stay clean and predictable:
| HTTP Method | CRUD Operation | Example | Meaning |
|---|---|---|---|
| GET | Read | GET /api/books/ | List all books |
| POST | Create | POST /api/books/ | Add a new book |
| GET | Read | GET /api/books/1/ | Fetch one book |
| PUT / PATCH | Update | PUT /api/books/1/ | Replace / edit a book |
| DELETE | Delete | DELETE /api/books/1/ | Remove a book |
REST also rests on a few guiding constraints worth remembering:
- Stateless: each request carries all the information needed to handle it β the server keeps no per-client session between calls.
- Clientβserver: the frontend (UI) and the backend (data) evolve independently.
- Uniform interface: resources are manipulated in a standard, predictable way.
- Representation-oriented: a resource can be delivered in multiple formats (JSON is by far the most common).
browser Β· mobile Β· SPA] -- HTTP request --> B[DRF API] B -- JSON response --> A B -- CRUD --> C[(Database)]
Why Use DRF Instead of Plain Django?
You could hand-write JSON responses in ordinary Django views with JsonResponse. For a single endpoint that is fine. But real APIs need validation, authentication, pagination, error formatting, and consistent structure β and re-writing all of that by hand becomes error-prone fast. DRF gives you these as batteries-included tools:
| DRF Feature | What it does for you |
|---|---|
| Serializers | Convert querysets/model instances to JSON and validate incoming data on the way back in |
| Authentication | Built-in Token, Session, and Basic auth; pluggable JWT and OAuth2 |
| Permissions | Fine-grained, declarative control over who may do what |
| ViewSets & Routers | Generate a full set of RESTful endpoints from a few lines |
| Browsable API | A human-friendly HTML interface for exploring and testing endpoints |
| Throttling | Rate-limit clients to protect your service |
| Content negotiation | Serve JSON, XML, or other formats based on the request |
β Where DRF shines
- Mobile backends β one API serving iOS and Android apps
- Single-page apps β powering React, Vue, or Angular frontends
- Public APIs β developer-facing access to your data
- Microservices β independent services in a distributed system
Installation & Setup
Getting DRF into a Django 5 project takes three short steps.
1. Install the package
pip install djangorestframework
2. Register it in settings.py
# settings.py
INSTALLED_APPS = [
# ...Django's own apps...
"rest_framework",
"catalog", # your app
]
# Optional but recommended project-wide defaults
REST_FRAMEWORK = {
"DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.PageNumberPagination",
"PAGE_SIZE": 10,
"DEFAULT_AUTHENTICATION_CLASSES": [
"rest_framework.authentication.SessionAuthentication",
"rest_framework.authentication.BasicAuthentication",
],
"DEFAULT_PERMISSION_CLASSES": [
"rest_framework.permissions.IsAuthenticatedOrReadOnly",
],
}
3. Wire up the login controls for the browsable API
# project urls.py
from django.urls import path, include
urlpatterns = [
# ...your app URLs...
path("api-auth/", include("rest_framework.urls")), # adds login/logout to the browsable API
]
β οΈ Set permissions deliberately
The default permission you choose applies to every view unless overridden. IsAuthenticatedOrReadOnly is a sane starting point (anyone can read, only signed-in users can write). Leaving it wide open with AllowAny is easy to forget and dangerous in production.
The Core Components
A DRF request flows through the same handful of pieces every time. Understanding this pipeline makes the rest of the framework click:
- Serializers β translate model instances β JSON and validate input. (The next lesson is dedicated to them.)
- Views & ViewSets β receive requests and return responses; ViewSets bundle related views together.
- Routers β auto-generate URL patterns for ViewSets so URLs stay consistent.
- Authentication & Permissions β authentication answers "who are you?"; permissions answer "what may you do?".
Your First API (Three Ways)
Let's expose a simple model as an API. We'll write the same functionality three times, each with less code, so you can see the progression DRF offers.
The model
# catalog/models.py
from django.db import models
class Book(models.Model):
title = models.CharField(max_length=200)
author = models.CharField(max_length=100)
published_date = models.DateField()
isbn = models.CharField(max_length=13, unique=True)
def __str__(self):
return self.title
The serializer
# catalog/serializers.py
from rest_framework import serializers
from .models import Book
class BookSerializer(serializers.ModelSerializer):
class Meta:
model = Book
fields = ["id", "title", "author", "published_date", "isbn"]
Approach A β function-based views
The @api_view decorator turns an ordinary function into a DRF view that understands request.data and returns a DRF Response.
# catalog/views.py
from rest_framework import status
from rest_framework.decorators import api_view
from rest_framework.response import Response
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)
# POST
serializer = BookSerializer(data=request.data)
serializer.is_valid(raise_exception=True) # returns a 400 with errors automatically
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
@api_view(["GET", "PUT", "DELETE"])
def book_detail(request, pk):
try:
book = Book.objects.get(pk=pk)
except Book.DoesNotExist:
return Response(status=status.HTTP_404_NOT_FOUND)
if request.method == "GET":
return Response(BookSerializer(book).data)
if request.method == "PUT":
serializer = BookSerializer(book, data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response(serializer.data)
# DELETE
book.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
Approach B β class-based views (APIView)
Class-based views group each HTTP method into its own method, which reads cleanly as the view grows.
# catalog/views.py
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from django.http import Http404
from .models import Book
from .serializers import BookSerializer
class BookList(APIView):
def get(self, request):
serializer = BookSerializer(Book.objects.all(), many=True)
return Response(serializer.data)
def post(self, request):
serializer = BookSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
class BookDetail(APIView):
def get_object(self, pk):
try:
return Book.objects.get(pk=pk)
except Book.DoesNotExist:
raise Http404
def get(self, request, pk):
return Response(BookSerializer(self.get_object(pk)).data)
def put(self, request, pk):
serializer = BookSerializer(self.get_object(pk), data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response(serializer.data)
def delete(self, request, pk):
self.get_object(pk).delete()
return Response(status=status.HTTP_204_NO_CONTENT)
Approach C β generic views (the least code)
DRF's generic views implement the standard CRUD patterns for you. This is the sweet spot for most everyday endpoints.
# catalog/views.py
from rest_framework import generics
from .models import Book
from .serializers import BookSerializer
class BookList(generics.ListCreateAPIView):
queryset = Book.objects.all()
serializer_class = BookSerializer
class BookDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = Book.objects.all()
serializer_class = BookSerializer
Wiring up the URLs
# catalog/urls.py
from django.urls import path
from . import views
urlpatterns = [
path("books/", views.BookList.as_view(), name="book-list"),
path("books/<int:pk>/", views.BookDetail.as_view(), name="book-detail"),
]
β Same behaviour, shrinking code
All three approaches expose identical endpoints: list, create, retrieve, update, and delete a book. Approach A is the most explicit (great for learning and unusual logic); Approach C is the most productive for standard CRUD. In the next lesson's follow-ups you'll meet ViewSets, which compress this even further.
The Browsable API
One of DRF's signature features is that when you open an API URL in a web browser, DRF renders a friendly HTML page instead of raw JSON β complete with forms for submitting data, a format switcher, and login controls. It is a built-in Postman that ships with your project.
π‘ Why it matters day to day
- Test endpoints without installing any external tool
- Inspect exactly what your serializer outputs
- Submit POST/PUT data through auto-generated forms
- Demo your API to teammates and stakeholders in seconds
In production you'll typically consume raw JSON. Requesting ?format=json or sending Accept: application/json bypasses the HTML view entirely.
Hands-on Exercise
ποΈ Build and Exercise a Book API
Objective: Stand up a working CRUD API and hit every endpoint in the browsable API.
Instructions:
- In a Django 5 project, create an app (e.g.
catalog), add theBookmodel above, and runmakemigrations+migrate. - Install DRF and add
"rest_framework"toINSTALLED_APPS. - Add the
BookSerializer, then implementBookListandBookDetailusing the generic views (Approach C). - Wire the URLs and add
path("api-auth/", include("rest_framework.urls")). - Run the server, visit
/books/in your browser, and use the form to create two books. - Visit
/books/1/and try PUT (edit) and DELETE.
π‘ Hint
If POST fails with a permission error, either sign in via the top-right login (thanks to api-auth/) or temporarily set DEFAULT_PERMISSION_CLASSES to ["rest_framework.permissions.AllowAny"] while developing. Remember the unique=True on isbn β using the same ISBN twice will (correctly) trigger a validation error.
β Expected outcome
GET /books/ returns a JSON array of your books. POST with a new title/author/date/ISBN returns 201 Created and the created object. PUT /books/1/ updates it, and DELETE /books/1/ returns 204 No Content. You wrote fewer than a dozen lines of view code to get all of it.
π― Quick Quiz
Question 1: In a RESTful API, which HTTP method is conventionally used to create a new resource?
Question 2: Which DRF view style requires the least code for standard CRUD on a model?
Question 3: What is the main purpose of the browsable API?
Summary & Quiz
π Key Takeaways
- DRF extends Django to build JSON APIs for mobile apps, SPAs, and other services.
- REST maps HTTP verbs (GET/POST/PUT/PATCH/DELETE) onto CRUD operations on URL-addressed resources.
- DRF's core pieces are serializers, views/ViewSets, routers, and auth/permissions.
- You can build the same CRUD API three ways β function-based, APIView, and generic views β with steadily less code.
- The browsable API lets you test everything from the browser.
π Further Reading
π What's Next?
Serializers are the heart of DRF β and they deserve a lesson of their own. Next we'll go deep on serializers for data transformation: field types, validation layers, nested relationships, and computed fields.
π Great start!
You've turned a Django model into a real API. Now let's master the piece that makes it all work β the serializer.