Skip to main content

πŸ”Œ 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 MethodCRUD OperationExampleMeaning
GETReadGET /api/books/List all books
POSTCreatePOST /api/books/Add a new book
GETReadGET /api/books/1/Fetch one book
PUT / PATCHUpdatePUT /api/books/1/Replace / edit a book
DELETEDeleteDELETE /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).
flowchart LR A[Client
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 FeatureWhat it does for you
SerializersConvert querysets/model instances to JSON and validate incoming data on the way back in
AuthenticationBuilt-in Token, Session, and Basic auth; pluggable JWT and OAuth2
PermissionsFine-grained, declarative control over who may do what
ViewSets & RoutersGenerate a full set of RESTful endpoints from a few lines
Browsable APIA human-friendly HTML interface for exploring and testing endpoints
ThrottlingRate-limit clients to protect your service
Content negotiationServe 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:

The Django REST Framework request pipeline A request enters a View or ViewSet, which is gated by authentication and permissions, uses a serializer to talk to the model and database, and returns a Response. Request Auth & Permissions View / ViewSet Serializer Model & Database JSON Response
Figure 1 β€” Every DRF request passes an auth/permission gate, is handled by a view, uses a serializer to move data to and from the model, and returns a Response.
  • 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.

sequenceDiagram participant Browser participant DRF as Browsable API participant View participant Serializer participant DB as Database Browser->>DRF: GET /api/books/ DRF->>View: dispatch request View->>DB: query books DB-->>View: queryset View->>Serializer: serialize Serializer-->>View: Python data View-->>DRF: Response DRF-->>Browser: HTML page with data & forms

πŸ’‘ 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:

  1. In a Django 5 project, create an app (e.g. catalog), add the Book model above, and run makemigrations + migrate.
  2. Install DRF and add "rest_framework" to INSTALLED_APPS.
  3. Add the BookSerializer, then implement BookList and BookDetail using the generic views (Approach C).
  4. Wire the URLs and add path("api-auth/", include("rest_framework.urls")).
  5. Run the server, visit /books/ in your browser, and use the form to create two books.
  6. 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.