Table of Contents

  1. Introduction
  2. Prerequisites
  3. What is Django REST Framework?
  4. Understanding REST API Principles
  5. Setting Up Your Development Environment
  6. Creating Your Django Project
  7. Building Your First REST API
  8. Implementing Authentication
  9. Adding Custom Permissions
  10. Testing Your API
  11. Project Structure
  12. Conclusion

Introduction

Building REST APIs with Django has never been easier thanks to Django REST Framework (DRF). This comprehensive tutorial will guide you through creating a complete RESTful API for a to-do list application from scratch.

By the end of this guide, you'll have a fully functional Django REST API that includes:

  • CRUD operations for managing tasks
  • JWT authentication system
  • Custom permissions for user-specific data
  • Professional project structure
  • Best practices for API development

Whether you're new to Django or looking to add API capabilities to your existing applications, this tutorial provides everything you need to get started with Django REST Framework.

Prerequisites

Before diving into this Django REST Framework tutorial, ensure you have:

  • Python 3.8+ installed on your system
  • Basic understanding of Python programming
  • Familiarity with Django fundamentals
  • Text editor or IDE (VS Code, PyCharm, etc.)
  • Command line/terminal access

What is Django REST Framework?

Django REST Framework (DRF) is a powerful toolkit for building Web APIs in Django. Built on top of Django, DRF provides:

Key Features of Django REST Framework

  • Serialization: Convert Django models to JSON/XML and vice versa
  • Authentication: Multiple authentication schemes (JWT, Token, Session)
  • Permissions: Fine-grained access control
  • Browsable API: Interactive web interface for testing
  • ViewSets: Simplified view logic for CRUD operations
  • Routers: Automatic URL routing

Why Choose Django REST Framework?

DRF is the industry standard for building APIs with Django because it:

  • Reduces development time significantly
  • Provides robust security features
  • Offers excellent documentation
  • Has a large, active community
  • Integrates seamlessly with Django

Understanding REST API Principles

What Makes an API RESTful?

REST (Representational State Transfer) APIs follow specific architectural constraints:

  1. Stateless: Each request contains all necessary information
  2. Resource-based: URLs represent resources, not actions
  3. HTTP Methods: Use standard HTTP verbs (GET, POST, PUT, DELETE)
  4. JSON Format: Consistent data format for requests/responses

HTTP Methods in REST APIs

Method Purpose Example
GET Retrieve data GET /api/tasks/ - Get all tasks
POST Create new resource POST /api/tasks/ - Create new task
PUT Update entire resource PUT /api/tasks/1/ - Update task 1
PATCH Partial update PATCH /api/tasks/1/ - Update specific fields
DELETE Remove resource DELETE /api/tasks/1/ - Delete task 1

RESTful URL Structure

For our to-do API, we'll use these endpoints:

/api/tasks/          # List all tasks, create new task
/api/tasks/{id}/     # Retrieve, update, or delete specific task
/api/auth/login/     # User authentication
/api/auth/refresh/   # Token refresh
HTTP Methods Demo

🚀 Interactive HTTP Methods Demo

📱
Client
🖥️
Server

Setting Up Your Development Environment

Step 1: Install Required Packages

Create a new directory for your project and set up a virtual environment:

# Create project directory
mkdir django-rest-api-tutorial
cd django-rest-api-tutorial

# Create virtual environment
python -m venv venv

# Activate virtual environment
# On Windows:
venv\Scripts\activate
# On macOS/Linux:
source venv/bin/activate

# Install Django and Django REST Framework
pip install django djangorestframework
pip install djangorestframework-simplejwt  # For JWT authentication

Step 2: Verify Installation

Confirm your installation by checking versions:

python -c "import django; print(django.get_version())"
python -c "import rest_framework; print('DRF installed successfully')"

Creating Your Django Project

Step 1: Initialize Django Project

django-admin startproject todoapi
cd todoapi

Step 2: Create Django App

python manage.py startapp tasks

Step 3: Configure Settings

Edit todoapi/settings.py to include DRF and your app:

# todoapi/settings.py
import os
from pathlib import Path
from datetime import timedelta

BASE_DIR = Path(__file__).resolve().parent.parent

# Add your apps and DRF to INSTALLED_APPS
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'rest_framework',
    'rest_framework_simplejwt',
    'tasks',  # Your app
]

# REST Framework configuration
REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework_simplejwt.authentication.JWTAuthentication',
    ],
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.IsAuthenticated',
    ],
    'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
    'PAGE_SIZE': 20,
}

# JWT Settings
SIMPLE_JWT = {
    'ACCESS_TOKEN_LIFETIME': timedelta(minutes=60),
    'REFRESH_TOKEN_LIFETIME': timedelta(days=7),
    'ROTATE_REFRESH_TOKENS': True,
}

Building Your First REST API

Step 1: Create the Task Model

Create your data model in tasks/models.py:

# tasks/models.py
from django.db import models
from django.contrib.auth.models import User

class Task(models.Model):
    PRIORITY_CHOICES = [
        ('low', 'Low'),
        ('medium', 'Medium'),
        ('high', 'High'),
    ]
    
    title = models.CharField(max_length=255)
    description = models.TextField(blank=True, null=True)
    completed = models.BooleanField(default=False)
    priority = models.CharField(max_length=10, choices=PRIORITY_CHOICES, default='medium')
    user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='tasks')
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
    due_date = models.DateTimeField(blank=True, null=True)

    class Meta:
        ordering = ['-created_at']
        
    def __str__(self):
        return self.title

Step 2: Create Database Migrations

python manage.py makemigrations
python manage.py migrate

Step 3: Build the Serializer

Create tasks/serializers.py:

# tasks/serializers.py
from rest_framework import serializers
from django.contrib.auth.models import User
from .models import Task

class TaskSerializer(serializers.ModelSerializer):
    user = serializers.StringRelatedField(read_only=True)
    
    class Meta:
        model = Task
        fields = [
            'id', 'title', 'description', 'completed', 
            'priority', 'user', 'created_at', 'updated_at', 'due_date'
        ]
        read_only_fields = ['id', 'user', 'created_at', 'updated_at']

    def validate_title(self, value):
        if len(value.strip()) < 3:
            raise serializers.ValidationError("Title must be at least 3 characters long.")
        return value

class UserSerializer(serializers.ModelSerializer):
    tasks_count = serializers.SerializerMethodField()
    
    class Meta:
        model = User
        fields = ['id', 'username', 'email', 'tasks_count']
        
    def get_tasks_count(self, obj):
        return obj.tasks.count()

Step 4: Create API Views

Build your views in tasks/views.py:

# tasks/views.py
from rest_framework import viewsets, status, filters
from rest_framework.decorators import action
from rest_framework.response import Response
from rest_framework.permissions import IsAuthenticated
from django_filters.rest_framework import DjangoFilterBackend
from .models import Task
from .serializers import TaskSerializer
from .permissions import IsOwnerOrReadOnly

class TaskViewSet(viewsets.ModelViewSet):
    serializer_class = TaskSerializer
    permission_classes = [IsAuthenticated, IsOwnerOrReadOnly]
    filter_backends = [DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter]
    filterset_fields = ['completed', 'priority']
    search_fields = ['title', 'description']
    ordering_fields = ['created_at', 'updated_at', 'due_date']
    ordering = ['-created_at']

    def get_queryset(self):
        """Return tasks for the current user only"""
        return Task.objects.filter(user=self.request.user)

    def perform_create(self, serializer):
        """Set the user when creating a new task"""
        serializer.save(user=self.request.user)

    @action(detail=False, methods=['get'])
    def completed(self, request):
        """Get all completed tasks"""
        completed_tasks = self.get_queryset().filter(completed=True)
        serializer = self.get_serializer(completed_tasks, many=True)
        return Response(serializer.data)

    @action(detail=False, methods=['get'])
    def pending(self, request):
        """Get all pending tasks"""
        pending_tasks = self.get_queryset().filter(completed=False)
        serializer = self.get_serializer(pending_tasks, many=True)
        return Response(serializer.data)

    @action(detail=True, methods=['post'])
    def mark_complete(self, request, pk=None):
        """Mark a task as completed"""
        task = self.get_object()
        task.completed = True
        task.save()
        serializer = self.get_serializer(task)
        return Response(serializer.data)

Step 5: Configure URLs

Create tasks/urls.py:

# tasks/urls.py
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from .views import TaskViewSet

router = DefaultRouter()
router.register(r'tasks', TaskViewSet, basename='tasks')

urlpatterns = [
    path('api/', include(router.urls)),
]

Update main todoapi/urls.py:

# todoapi/urls.py
from django.contrib import admin
from django.urls import path, include
from rest_framework_simplejwt.views import (
    TokenObtainPairView,
    TokenRefreshView,
)

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include('tasks.urls')),
    path('api/auth/login/', TokenObtainPairView.as_view(), name='token_obtain_pair'),
    path('api/auth/refresh/', TokenRefreshView.as_view(), name='token_refresh'),
]
REST API Endpoints Demo
🚀 Django REST API Endpoints Explorer
📋 Available Endpoints
GET
/api/tasks/
List all tasks
Query Parameters:
completed boolean - Filter by completion status
priority string - Filter by priority (low, medium, high)
search string - Search in title and description
POST
/api/tasks/
Create new task
Required Fields:
title string - Task title (min 3 chars)
Optional Fields:
description string - Detailed description
priority string - Priority level
due_date datetime - Due date
GET
/api/tasks/{id}/
Get specific task
URL Parameters:
id integer - Task ID
PUT
/api/tasks/{id}/
Update task
URL Parameters:
id integer - Task ID
Body Fields:
Same as POST endpoint
DELETE
/api/tasks/{id}/
Delete task
URL Parameters:
id integer - Task ID
POST
/api/auth/login/
Get JWT tokens
Required Fields:
username string - User username
password string - User password
🔧 API Demo
👈 Click on an endpoint to see the demo

Implementing Authentication

Step 1: Create Superuser

python manage.py createsuperuser

Step 2: Test Authentication

Start your development server:

python manage.py runserver

Obtain JWT tokens by making a POST request to http://127.0.0.1:8000/api/auth/login/:

{
    "username": "your_username",
    "password": "your_password"
}

You'll receive:

{
    "access": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...",
    "refresh": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9..."
}

Step 3: Use Access Token

Include the access token in your API requests:

Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...

Adding Custom Permissions

Create tasks/permissions.py:

# tasks/permissions.py
from rest_framework import permissions

class IsOwnerOrReadOnly(permissions.BasePermission):
    """
    Custom permission to only allow owners of an object to edit it.
    """

    def has_object_permission(self, request, view, obj):
        # Read permissions are allowed to any request,
        # so we'll always allow GET, HEAD or OPTIONS requests.
        if request.method in permissions.SAFE_METHODS:
            return True

        # Write permissions are only allowed to the owner of the task.
        return obj.user == request.user

Testing Your API

Manual Testing with cURL

  1. Get access token:
curl -X POST http://127.0.0.1:8000/api/auth/login/ \
  -H "Content-Type: application/json" \
  -d '{"username": "your_username", "password": "your_password"}'
  1. Create a task:
curl -X POST http://127.0.0.1:8000/api/tasks/ \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"title": "Learn Django REST Framework", "priority": "high"}'
  1. List all tasks:
curl -X GET http://127.0.0.1:8000/api/tasks/ \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"

Using Django REST Framework Browsable API

Navigate to http://127.0.0.1:8000/api/tasks/ in your browser to access the interactive API interface.

Project Structure

After completing this tutorial, your project structure will look like:

todoapi/
├── manage.py
├── todoapi/
│   ├── __init__.py
│   ├── settings.py
│   ├── urls.py
│   ├── wsgi.py
│   └── asgi.py
└── tasks/
    ├── __init__.py
    ├── admin.py
    ├── apps.py
    ├── models.py
    ├── serializers.py
    ├── views.py
    ├── permissions.py
    ├── urls.py
    ├── tests.py
    └── migrations/
        ├── __init__.py
        └── 0001_initial.py

File Descriptions

  • models.py: Contains your Task model with all necessary fields
  • serializers.py: Defines how model data is converted to/from JSON
  • views.py: Contains your API logic and endpoint handlers
  • permissions.py: Custom permission classes for access control
  • urls.py: URL routing configuration for your API endpoints
  • migrations/: Database migration files for your models

Best Practices and Tips

1. API Versioning

Consider implementing API versioning for future updates:

# In urls.py
urlpatterns = [
    path('api/v1/', include('tasks.urls')),
]

2. Error Handling

Implement proper error handling in your views:

from rest_framework.exceptions import ValidationError

def perform_create(self, serializer):
    if Task.objects.filter(user=self.request.user, title=serializer.validated_data['title']).exists():
        raise ValidationError("Task with this title already exists.")
    serializer.save(user=self.request.user)

3. API Documentation

Add documentation using tools like drf-spectacular:

pip install drf-spectacular

4. Testing

Write comprehensive tests for your API endpoints:

# tests.py
from django.test import TestCase
from rest_framework.test import APIClient
from django.contrib.auth.models import User

class TaskAPITestCase(TestCase):
    def setUp(self):
        self.client = APIClient()
        self.user = User.objects.create_user(username='testuser', password='testpass')
        
    def test_create_task(self):
        self.client.force_authenticate(user=self.user)
        response = self.client.post('/api/tasks/', {'title': 'Test Task'})
        self.assertEqual(response.status_code, 201)

Conclusion

Congratulations! You've successfully built a complete REST API using Django REST Framework. This tutorial covered:

  • Setting up Django and DRF from scratch
  • Creating models, serializers, and views
  • Implementing JWT authentication
  • Adding custom permissions
  • Following REST API best practices
  • Testing your API endpoints

Next Steps

To further enhance your Django REST API skills, consider:

  1. Adding more complex relationships between models
  2. Implementing file upload functionality
  3. Adding real-time features with Django Channels
  4. Deploying your API to production (Heroku, AWS, DigitalOcean)
  5. Creating a frontend to consume your API (React, Vue.js, Angular)
  6. Adding comprehensive testing and CI/CD pipelines

Additional Resources

Building REST APIs with Django REST Framework opens up countless possibilities for creating scalable, maintainable web services. Whether you're building a simple CRUD API or a complex system with multiple integrations, DRF provides the tools and flexibility you need to succeed.

Start building your next API project today and join the thousands of developers who trust Django REST Framework for their web service needs!