Build Backend APIs with DRF

Build Backend APIs with DRF

DataScience4

πŸ“Œ How to Use the Django REST Framework - Build Backend APIs with DRF

Django is a powerful, high-level Python web framework that encourages rapid development and clean, pragmatic design. While it's excellent for building traditional server-rendered websites, its true power in the modern web stack is often unlocked by pairing it with the Django REST Framework (DRF) to build robust, scalable, and secure Web APIs.

This comprehensive guide will walk you through building a complete backend API from scratch using DRF. We will cover the core concepts, from setting up your project to creating endpoints with full CRUD (Create, Read, Update, Delete) functionality.

---

Part 1: Understanding the Core Concepts

#### What is a REST API?

Before diving into DRF, let's quickly recap what a REST (REpresentational State Transfer) API is. It's an architectural style for designing networked applications. The key principles are:

β€’ Resources: Everything is a "resource." A resource could be a user, a blog post, a product, etc. These are identified by URIs (e.g., /api/posts/1/).
β€’ HTTP Verbs: You interact with resources using standard HTTP methods:
GET: Retrieve a resource.
POST: Create a new resource.
PUT / PATCH: Update an existing resource.
DELETE: Remove a resource.
β€’ Statelessness: Each request from a client to the server must contain all the information needed to understand and complete the request. The server does not store any client context between requests.
β€’ JSON as a Standard Format: While REST is format-agnostic, JSON (JavaScript Object Notation) has become the de-facto standard for sending and receiving data.

#### Why Django REST Framework?

DRF is a toolkit built on top of Django that makes building REST APIs incredibly efficient. Its key features include:

β€’ The Browsable API: A human-friendly HTML interface for your API, generated automatically, which is invaluable for development and testing.
β€’ Serialization: A powerful serialization engine that can convert Django QuerySets and model instances to and from JSON (and other formats).
β€’ Authentication & Permissions: Includes policies for handling API authentication (e.g., OAuth1, OAuth2, Session) and permissions (e.g., IsAuthenticated, IsAdminUser).
β€’ Generic Views & ViewSets: Provides high-level abstractions that drastically reduce the amount of boilerplate code you need to write for common API patterns.
β€’ Routers: Automatically generate URL patterns for your ViewSets, saving you from manually wiring up every endpoint.

---

Part 2: Project Setup and Installation

Let's start by setting up our project environment. We'll build a simple API for a blog.

1. Create a Virtual Environment

It's best practice to isolate your project's dependencies.

# Create a project directory
mkdir drf_blog && cd drf_blog

# Create and activate a virtual environment
python -m venv venv
source venv/bin/activate # On Windows, use `venv\Scripts\activate`

2. Install Django and DRF

pip install django djangorestframework

3. Create the Django Project and App

# Create the Django project named 'core' in the current directory
django-admin startproject core .

# Create a 'blog' app to house our API logic
python manage.py startapp blog

Your directory structure should now look like this:

drf_blog/
β”œβ”€β”€ core/
β”‚ β”œβ”€β”€ __init__.py
β”‚ β”œβ”€β”€ asgi.py
β”‚ β”œβ”€β”€ settings.py
β”‚ β”œβ”€β”€ urls.py
β”‚ └── wsgi.py
β”œβ”€β”€ blog/
β”‚ β”œβ”€β”€ __init__.py
β”‚ β”œβ”€β”€ admin.py
β”‚ β”œβ”€β”€ apps.py
β”‚ β”œβ”€β”€ migrations/
β”‚ β”œβ”€β”€ models.py
β”‚ β”œβ”€β”€ tests.py
β”‚ └── views.py
β”œβ”€β”€ manage.py
└── venv/

4. Configure Settings

Open core/settings.py and register rest_framework and our new blog app in the INSTALLED_APPS list.

# core/settings.py

INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',

# Third-party apps
'rest_framework',

# Local apps
'blog',
]

---

Part 3: Models and Serializers - Defining the Data

#### 1. Define the Django Models

First, we need to define the structure of our data. We'll have Post and Category models.

Open blog/models.py and add the following:

# blog/models.py

from django.db import models
from django.contrib.auth.models import User

class Category(models.Model):
name = models.CharField(max_length=100)

def __str__(self):
return self.name

class Post(models.Model):
title = models.CharField(max_length=255)
content = models.TextField()
author = models.ForeignKey(User, on_delete=models.CASCADE)
category = models.ForeignKey(Category, on_delete=models.SET_NULL, null=True, related_name='posts')
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)

def __str__(self):
return self.title

#### 2. Create and Apply Migrations

Now, let's tell Django to create the database tables for these models.

python manage.py makemigrations
python manage.py migrate

#### 3. What are Serializers?

Serializers are the heart of DRF. Their job is to translate data.

β€’ Serialization: When a client requests data (e.g., GET /api/posts/), the serializer takes a complex data type, like a Django model instance or QuerySet, and converts it into a simple, transmittable format like JSON.
β€’ Deserialization: When a client sends data (e.g., POST /api/posts/), the serializer takes the incoming JSON payload, validates it, and converts it back into a complex type, like a Python object or a Django model instance that can be saved to the database.

#### 4. Create the Serializers

Create a new file blog/serializers.py and add the following:

# blog/serializers.py

from rest_framework import serializers
from .models import Post, Category
from django.contrib.auth.models import User

class CategorySerializer(serializers.ModelSerializer):
class Meta:
model = Category
fields = ['id', 'name']

class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ['id', 'username', 'email']

class PostSerializer(serializers.ModelSerializer):
# Use StringRelatedField to show the category name instead of its ID
category = serializers.StringRelatedField()
# Use UserSerializer for a nested representation of the author
author = UserSerializer(read_only=True)

class Meta:
model = Post
fields = [
'id',
'title',
'content',
'author',
'category',
'created_at',
'updated_at'
]

Here, we are using ModelSerializer, which is a DRF shortcut. It automatically generates fields based on the model definition, along with default validators. We've customized it slightly:

β€’ category = serializers.StringRelatedField(): By default, a ForeignKey would be represented by its primary key (ID). StringRelatedField uses the __str__ method of the related model (Category), which is more human-readable.
β€’ author = UserSerializer(read_only=True): We've created a nested serializer to display detailed author information. read_only=True means this field will be used for serialization (reading data) but not for deserialization (creating/updating data). When creating a post, the author will be set programmatically from the logged-in user.

---

Part 4: Views and URLs - The API Endpoints

Views handle the logic for processing an incoming request and returning a response. DRF provides powerful generic views and ViewSets to simplify this process.

#### What are ViewSets?

A ViewSet is a class that combines the logic for a set of related views. For a resource like Post, you typically need endpoints for listing, creating, retrieving, updating, and deleting. Instead of creating five separate view classes, a ViewSet handles all of these actions in one place.

The ModelViewSet is particularly powerful. It provides default implementations for all the standard CRUD operations (list, create, retrieve, update, destroy) with almost no code.

#### 1. Create the Views

Open blog/views.py and replace its content with the following:

# blog/views.py

from rest_framework import viewsets
from .models import Post, Category
from .serializers import PostSerializer, CategorySerializer
from rest_framework.permissions import IsAuthenticatedOrReadOnly

class PostViewSet(viewsets.ModelViewSet):
"""
API endpoint that allows posts to be viewed or edited.
"""
queryset = Post.objects.all().order_by('-created_at')
serializer_class = PostSerializer
permission_classes = [IsAuthenticatedOrReadOnly]

def perform_create(self, serializer):
# Automatically set the author to the logged-in user upon creation
serializer.save(author=self.request.user)


class CategoryViewSet(viewsets.ModelViewSet):
"""
API endpoint that allows categories to be viewed or edited.
"""
queryset = Category.objects.all()
serializer_class = CategorySerializer
permission_classes = [IsAuthenticatedOrReadOnly]

That's it! In just a few lines of code, we have defined the entire logic for our Post and Category APIs.

β€’ queryset: This defines the base set of objects that are available for this view.
β€’ serializer_class: This tells the ViewSet which serializer to use for converting the data.
β€’ permission_classes: We've added a simple permission. IsAuthenticatedOrReadOnly allows any user (authenticated or not) to view the data (GET requests), but only authenticated users can create, update, or delete it.
β€’ perform_create: We override this method to inject the currently logged-in user (self.request.user) as the author when a new post is created.

#### 2. Wire up the URLs with Routers

Now we need to create the URL endpoints. DRF's Routers work with ViewSets to automatically generate the URL patterns.

First, create a blog/urls.py file:

# blog/urls.py

from django.urls import path, include
from rest_framework.routers import DefaultRouter
from .views import PostViewSet, CategoryViewSet

# Create a router and register our viewsets with it.
router = DefaultRouter()
router.register(r'posts', PostViewSet, basename='post')
router.register(r'categories', CategoryViewSet, basename='category')

# The API URLs are now determined automatically by the router.
urlpatterns = [
path('', include(router.urls)),
]

The DefaultRouter will generate the following URL patterns for us:

β€’ /posts/ - GET (list), POST (create)
β€’ /posts/<pk>/ - GET (retrieve), PUT (update), PATCH (partial update), DELETE (destroy)
β€’ And the same for /categories/.

Finally, we need to include these app-level URLs in our project's main URL configuration.

Open core/urls.py and modify it:

# core/urls.py

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
path('admin/', admin.site.urls),
# Include the blog app's API URLs under the 'api/' namespace
path('api/', include('blog.urls')),
]

---

Part 5: Testing the API

Our API is now fully functional. Let's test it.

1. Create a Superuser and Add Some Data

First, we need a user to test authentication and some data to view.

python manage.py createsuperuser

Follow the prompts to create an admin user. Now, run the development server:

python manage.py runserver

Navigate to http://127.0.0.1:8000/admin/. Log in with your superuser credentials and use the admin interface to add a few Category and Post objects.

2. Use the Browsable API

Now for the magic of DRF. Open your web browser and go to http://127.0.0.1:8000/api/posts/.

You will see the Browsable API, a beautifully rendered HTML page showing a list of your posts in JSON format.

β€’ You can click on the URL of a specific post to navigate to its detail view (e.g., http://127.0.0.1:8000/api/posts/1/).
β€’ Scroll to the bottom of the list view page. You'll see an HTML form that allows you to POST a new post directly from the browser.
β€’ To test the permissions, log out of the Django admin. You will still be able to view the posts. However, if you try to use the form to create a new one, you'll get a "403 Forbidden" error because you are not authenticated. Log back in, and you'll be able to create, update, and delete posts.

3. Use a Command-Line Tool like curl

The Browsable API is for humans. For programmatic access, you would use a tool like curl or an HTTP client like Postman.

β€’ Get all posts (GET):

curl -X GET http://127.0.0.1:8000/api/posts/

β€’ Create a new post (POST):
This requires authentication. You'll need to log in via the browsable API first to get a session cookie, or use a more robust authentication method like Token Authentication. Assuming you are logged in, you can simulate a form post.

# Note: Replace <your_csrf_token> and <your_session_id> from browser cookies
curl -X POST http://127.0.0.1:8000/api/posts/ \
-H "Content-Type: application/json" \
-H "X-CSRFToken: <your_csrf_token>" \
-b "sessionid=<your_session_id>" \
-d '{"title": "New Post via cURL", "content": "This is the content.", "category": 1}'

Note: Managing authentication with curl via cookies is complex. For real applications, you would implement Token or JWT authentication.

β€’ Update a post (PUT):

# Update post with ID 1
curl -X PUT http://127.0.0.1:8000/api/posts/1/ \
-H "Content-Type: application/json" \
-H "X-CSRFToken: <your_csrf_token>" \
-b "sessionid=<your_session_id>" \
-d '{"title": "Updated Title", "content": "Updated content.", "category": 1}'

β€’ Delete a post (DELETE):

# Delete post with ID 1
curl -X DELETE http://127.0.0.1:8000/api/posts/1/ \
-H "X-CSRFToken: <your_csrf_token>" \
-b "sessionid=<your_session_id>"

Conclusion and Next Steps

You have successfully built a powerful, feature-complete REST API using Django and the Django REST Framework. You've learned the core workflow:

β€’ Models: Define your data structure.
β€’ Serializers: Define how your data is represented and validated.
β€’ ViewSets: Define the logic for your API endpoints.
β€’ Routers & URLs: Wire up the endpoints for access.

This foundation is incredibly strong. From here, you can explore more advanced DRF features to build production-ready applications:

β€’ Authentication: Implement Token-based or JWT (JSON Web Token) authentication for stateless client-server communication.
β€’ Permissions: Write custom permission classes to handle complex authorization logic (e.g., only the author of a post can edit it).
β€’ Filtering and Searching: Add dynamic filtering backends to allow clients to query your API (e.g., /api/posts/?category=Technology).
β€’ Pagination: Configure pagination to handle large datasets efficiently.
β€’ Testing: Write automated tests for your API endpoints to ensure they are reliable and bug-free.

Report Page