Skip to content

DRF Fundamentals

DRF (Django REST Framework) is the standard toolkit for building APIs in Django projects. It gives you a clean structure for handling request parsing, validation, business logic, authentication, permissions, and response formatting. In real projects, DRF helps you move from “just returning JSON” to a proper, consistent API setup that frontend apps, mobile apps, and other services can rely on.

After this chapter, you should be able to explain how an API request moves through a DRF app, know when to use each HTTP method, understand status codes correctly, and understand the main DRF building blocks such as serializers, views, and authentication.

API Thinking

Learn how clients and servers talk to each other using requests and responses.

REST Principles

Understand resources, endpoints, and stateless design.

DRF Building Blocks

Connect serializers, views, and routers together into a maintainable API.

  1. Install DRF in your Django project:

    Terminal window
    uv add djangorestframework
  2. Register DRF in Django settings:

    # settings.py
    INSTALLED_APPS = [
    # other apps
    "rest_framework",
    ]
  3. Add baseline REST framework configuration:

    # settings.py
    REST_FRAMEWORK = {
    # Keep decimals as numbers in JSON responses instead of converting to strings (optional, but often desirable for APIs that return numeric data)
    'COERCE_DECIMAL_TO_STRING': False,
    }

An API (Application Programming Interface) is a proper communication layer between two software systems. Instead of exposing your internal database structure or app code directly, an API exposes carefully designed endpoints instead. Each endpoint accepts certain inputs and returns structured outputs, which lets different systems work together without being tightly tied to each other’s internal code.

In simple words, an API is like a contract. The client agrees to send requests in a known format, and the server agrees to process those requests and send back predictable responses. This contract lets teams work independently: backend engineers can keep changing the internal code, while frontend engineers keep building on top of the same stable API.

The diagram below shows the same flow in a simple visual form:

graph LR Client --> API API --> DB DB --> API API --> Client Client[Client App] API[API Server] DB[Database]

This flow is at the heart of backend API design. The client asks for something, the API server checks and processes that request, the database gives back the stored data, and the API then sends back a properly formatted response. DRF sits inside the API server layer and gives you ready-made tools for input validation, serialization, authentication, and permission checks.

REST (Representational State Transfer) is a style of designing APIs, not a strict set of rules you must follow exactly. A RESTful API organizes data into “resources” and exposes them through URLs. Clients work with these resources using HTTP methods such as GET, POST, PUT, PATCH, and DELETE.

One key REST idea is being stateless. Every request should carry everything the server needs (authentication token, parameters, data), so the server does not need to remember anything from previous requests. This stateless design makes APIs easier to scale, cache, test, and debug.

graph LR R["Resource: users"] --> E1["GET /users/"] R --> E2["POST /users/"] R --> E3["GET /users/:id/"] R --> E4["PATCH /users/:id/"] R --> E5["DELETE /users/:id/"]

HTTP methods tell the server what you actually want to do. Choosing the right method makes your API predictable and easy to understand.

GET is used to fetch data and should not change anything on the server. It is considered a safe method and is often cached by browsers and proxies.

Status codes tell you what actually happened with your request. They are not just extra detail, they are part of the API’s contract and help the client decide what to do next.

graph TD A[HTTP Response] --> B[1xx Informational] A --> C[2xx Success] A --> D[3xx Redirection] A --> E[4xx Client Error] A --> F[5xx Server Error]
Status CodeMeaningTypical DRF Scenario
200 OKRequest worked and the response has data in itList or retrieve endpoint
201 CreatedA new resource was created successfullySuccessful POST create
204 No ContentRequest worked but there is no body in the responseSuccessful DELETE
400 Bad RequestThe input failed validationSerializer errors
401 UnauthorizedThe user is not logged in / not authenticatedMissing/invalid auth token
403 ForbiddenThe user is logged in but is not allowed to do thisPermission denied
404 Not FoundThe resource does not existInvalid object ID
500 Internal Server ErrorSomething unexpected went wrong on the serverUnhandled backend exception

JSON (JavaScript Object Notation) is the default data format used by most REST APIs, because it does not depend on any specific programming language, it is lightweight, and it is easy to read in browsers, mobile apps, and backend services. In DRF, serializers turn Django model instances into JSON-friendly Python values, and then renderers turn that data into an actual JSON response.

A good JSON response stays consistent and well documented. Field names, whether a value can be empty (null), nested structures, and data types should stay the same over time, so that clients do not suddenly break.

{
"id": 1,
"name": "Kumar Sahil",
"email": "krsahil8825@gmail.com",
"is_active": true,
"roles": ["admin", "editor"]
}

Serializer

Turns model/queryset data into JSON, and checks incoming request data before it gets saved.

APIView / ViewSet

Receives the HTTP request, applies authentication and permission checks, calls the serializer, and sends back the response.

Router

Automatically creates URL patterns for ViewSets, so your API routing stays consistent.

Authentication

Checks who the user actually is, for example through session, token, or JWT login.

Permission

Decides what the user is allowed to do, for example read-only access for users who are not logged in.

Pagination

Splits large lists of results into smaller pages, for better performance and a better experience for the client.

graph TD Req[Incoming Request] --> Auth[Authentication] Auth --> Perm[Permission Check] Perm --> View[APIView or ViewSet] View --> Ser[Serializer Validate or Serialize] Ser --> DB[(Database)] DB --> Ser Ser --> Res[JSON Response]
EndpointMethodPurpose
/users/GETReturn a paginated list of users
/users/POSTCreate a new user
/users/{id}/GETGet one single user
/users/{id}/PATCHPartially update a user
/users/{id}/DELETERemove a user
  • Django: Focuses on building HTML pages that are rendered by the server, along with forms and templates. It is great for traditional web apps where the server creates the whole page itself. Django’s views and forms are built around this request-response cycle.

  • DRF: Focuses on building APIs that return structured data (usually JSON) for clients like single-page apps, mobile apps, or other services to use. DRF gives you serializers for validating and converting data, and views that handle API-specific things like authentication and permissions.

  • Use Django views and templates when you are building a single, all-in-one web app where the server creates and sends the HTML pages directly to users.

  • Use DRF when you need to expose an API for frontend frameworks (React, Vue), mobile apps, or other services to talk to. DRF lets you keep a clean separation between your backend logic and how clients use your data.

Django and DRF each have their own request and response objects. DRF’s Request builds on top of Django’s HttpRequest and adds extra features for reading request data, handling authentication, and managing content negotiation. In the same way, DRF’s Response builds on top of Django’s HttpResponse and makes it easier to send back JSON responses with the right status code and content type.

To use DRF’s Request and Response, you usually import them from rest_framework and use them inside your API views. For example:

# views.py
from rest_framework.views import APIView
from rest_framework.response import Response
@api_view()
def my_api_view(request):
# DRF's Request object is used here
data = {"message": "Hello, DRF!"}
# DRF's Response object is used to return JSON data
return Response(data)

The @api_view decorator is a function from DRF that lets you create function-based API views. You give it a list of HTTP methods (for example, ['GET', 'POST']) that the view should respond to. When you use @api_view, DRF automatically wraps your function so it can properly handle API requests and responses, including content negotiation, authentication, and permission checks.

By default, an api view only accepts the GET method. So if you want to allow other methods like POST or PATCH, you need to list them inside the decorator. For example:

from rest_framework.decorators import api_view
@api_view(['GET', 'POST'])
def my_api_view(request):
if request.method == 'GET':
# Handle GET request
pass
elif request.method == 'POST':
# Handle POST request
pass

The status module in DRF gives you a set of named constants for HTTP status codes. Instead of trying to remember number codes like 200, 404, or 500, you can use clear names like status.HTTP_200_OK, status.HTTP_404_NOT_FOUND, and status.HTTP_500_INTERNAL_SERVER_ERROR. This makes your code easier to read and makes it obvious what each response status actually means.

When sending back a response in DRF, you can use the status module to set the right status code. For example:

from rest_framework import status
from rest_framework.response import Response
def my_api_view(request):
data = {"message": "Hello, DRF!"}
return Response(data, status=status.HTTP_200_OK)