Building a Complete API Project

It expands your outline into a detailed professional post suitable for blogs, documentation, or tutorials.
It includes text, code, and structured explanations only — no icons, emojis, or non-text content.

Introduction

When learning Django REST Framework (DRF), the best way to truly understand how everything fits together is to build a complete API project. So far, you might have worked with serializers, viewsets, and routers individually. In this post, we’ll combine all these components into a small, fully functional RESTful API.

Our project will implement an API for managing books. Each book will have a title, an author (a user), and a publication date. You’ll learn how to integrate authentication, filtering, searching, and ordering — all powered by Django REST Framework.

By the end of this post, you’ll have a complete, production-ready API that supports:

  • CRUD (Create, Read, Update, Delete) operations
  • Token-based authentication
  • Filtering, searching, and ordering
  • Pagination and permissions

Let’s begin step by step.

Step 1: Setting Up the Django Project

First, create a new Django project. Make sure you have Python and Django installed.

pip install django djangorestframework django-filter
django-admin startproject library_project
cd library_project
python manage.py startapp books

Once the app is created, add both rest_framework and books to your INSTALLED_APPS in library_project/settings.py:

INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'rest_framework',
'rest_framework.authtoken',
'django_filters',
'books',
]

We’ve also added rest_framework.authtoken and django_filters for authentication and filtering support.

Now let’s define our model.


Step 2: Creating the Book Model

Open books/models.py and create the Book model.

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

class Book(models.Model):
title = models.CharField(max_length=100)
author = models.ForeignKey(User, on_delete=models.CASCADE)
published_date = models.DateField()
def __str__(self):
    return self.title

This model defines a simple data structure:

  • title: a short text field for the book’s title
  • author: a relationship to the built-in Django User model
  • published_date: a DateField to record when the book was published

Then run migrations:

python manage.py makemigrations
python manage.py migrate

Step 3: Creating a Serializer

A serializer converts the Book model into JSON format and validates incoming data when creating or updating records.

Create books/serializers.py:

from rest_framework import serializers
from .models import Book

class BookSerializer(serializers.ModelSerializer):
class Meta:
    model = Book
    fields = '__all__'

Using ModelSerializer allows DRF to automatically generate fields and handle validation based on the model definition. You can customize validation later if needed.


Step 4: Creating the ViewSet

A ViewSet in Django REST Framework handles all CRUD operations in a single class. Instead of writing multiple views, DRF provides a unified way to manage Create, Read, Update, and Delete actions.

Open books/views.py and add the following:

from rest_framework import viewsets, permissions, filters
from django_filters.rest_framework import DjangoFilterBackend
from .models import Book
from .serializers import BookSerializer

class BookViewSet(viewsets.ModelViewSet):
queryset = Book.objects.all()
serializer_class = BookSerializer
permission_classes = [permissions.IsAuthenticated]
filter_backends = [DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter]
filterset_fields = ['author']
search_fields = ['title']
ordering_fields = ['published_date']

Explanation

  • queryset defines the default set of data available for the API.
  • serializer_class specifies how to convert model instances to JSON and back.
  • permission_classes ensures only authenticated users can access the endpoints.
  • filter_backends enables built-in filtering and searching.
  • filterset_fields allows users to filter by the author field.
  • search_fields adds text search on the book title.
  • ordering_fields enables sorting results by publication date.

With this configuration, your API automatically supports querying, filtering, and ordering without writing custom logic.


Step 5: Registering the ViewSet with a Router

Routers in DRF automatically create URL patterns for your ViewSets.
Create a new file named books/urls.py:

from rest_framework.routers import DefaultRouter
from .views import BookViewSet

router = DefaultRouter()
router.register(r'books', BookViewSet)
urlpatterns = router.urls

Then include this in your project’s main urls.py:

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

urlpatterns = [
path('admin/', admin.site.urls),
path('api/', include('books.urls')),
path('api-auth/', include('rest_framework.urls')),
]

Now run the development server:

python manage.py runserver

Visit
http://127.0.0.1:8000/api/books/
You’ll see the Django REST Framework’s interactive API browser.


Step 6: Testing Basic CRUD Operations

Before adding authentication, let’s test basic CRUD operations.
You can use Postman, cURL, or simply the DRF browser interface.

Create a Book (POST)

Endpoint: /api/books/

Example Request:

{
  "title": "Learning Django",
  "author": 1,
  "published_date": "2024-05-20"
}

Example Response:

{
  "id": 1,
  "title": "Learning Django",
  "author": 1,
  "published_date": "2024-05-20"
}

Read Books (GET)

Endpoint: /api/books/

Returns a list of all books.

Retrieve Single Book

Endpoint: /api/books/1/

Update a Book (PUT or PATCH)

Endpoint: /api/books/1/

Example Request:

{
  "title": "Learning Django REST Framework",
  "author": 1,
  "published_date": "2024-06-01"
}

Delete a Book (DELETE)

Endpoint: /api/books/1/

This removes the record from the database.

At this point, all CRUD operations are functional.


Step 7: Enabling Token Authentication

Next, we’ll secure our API using Token Authentication. This ensures that only registered users can access the data.

Update Settings

In settings.py, configure the default authentication classes:

REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
    'rest_framework.authentication.TokenAuthentication',
    'rest_framework.authentication.SessionAuthentication',
],
'DEFAULT_PERMISSION_CLASSES': [
    'rest_framework.permissions.IsAuthenticated',
]
}

Create Tokens for Users

Add the following to your app’s urls.py:

from rest_framework.authtoken import views
from django.urls import path

urlpatterns += [
path('token/', views.obtain_auth_token),
]

Now you can request a token by sending a POST request to /api/token/ with valid user credentials.

Example Request:

{
  "username": "john",
  "password": "password123"
}

Example Response:

{
  "token": "ab23b3e9d5fa28f7c431b12c93e7b27d31af21e2"
}

Use this token in subsequent API calls:

Authorization: Token ab23b3e9d5fa28f7c431b12c93e7b27d31af21e2

Now only authenticated users can perform CRUD operations.


Step 8: Filtering, Searching, and Ordering

Our BookViewSet already includes filtering and searching capabilities. Let’s explore how they work.

Filtering

You can filter books by author using a query parameter:

/api/books/?author=1

This returns only books written by the user with ID 1.

Searching

Use the search parameter to look for titles:

/api/books/?search=Django

DRF performs a case-insensitive search on the title field.

Ordering

You can sort results by the published date:

/api/books/?ordering=published_date

To reverse the order:

/api/books/?ordering=-published_date

With just a few lines in your ViewSet, your API now supports advanced querying features.


Step 9: Pagination

Large datasets should be paginated to avoid overwhelming users or the server.
Enable pagination in settings.py:

REST_FRAMEWORK = {
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
'PAGE_SIZE': 10
}

Now when listing books, responses will include pagination metadata:

{
  "count": 25,
  "next": "http://127.0.0.1:8000/api/books/?page=2",
  "previous": null,
  "results": [
{
  "id": 1,
  "title": "Learning Django",
  "author": 1,
  "published_date": "2024-05-20"
}
] }

Step 10: Testing with the Django Admin

Django’s admin panel is automatically available for managing books and users.

Register the Model

In books/admin.py:

from django.contrib import admin
from .models import Book

admin.site.register(Book)

Run the server, visit /admin/, and log in with your superuser credentials.
You can create and edit books manually, which is helpful for testing the API.


Step 11: Implementing Custom Behavior in ViewSet

You can easily override default behaviors. For instance, automatically associate the current user as the book author:

class BookViewSet(viewsets.ModelViewSet):
queryset = Book.objects.all()
serializer_class = BookSerializer
permission_classes = [permissions.IsAuthenticated]
filter_backends = [DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter]
filterset_fields = ['author']
search_fields = ['title']
ordering_fields = ['published_date']
def perform_create(self, serializer):
    serializer.save(author=self.request.user)

Now, when a user creates a book, the author field will automatically be set to the authenticated user.


Step 12: Handling Permissions More Precisely

Sometimes you want to restrict editing or deleting to the user who created the record.
You can use custom permissions.

Create books/permissions.py:

from rest_framework import permissions

class IsAuthorOrReadOnly(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
    if request.method in permissions.SAFE_METHODS:
        return True
    return obj.author == request.user

Update BookViewSet to use it:

from .permissions import IsAuthorOrReadOnly

class BookViewSet(viewsets.ModelViewSet):
queryset = Book.objects.all()
serializer_class = BookSerializer
permission_classes = [IsAuthorOrReadOnly]

Now users can view all books but can edit or delete only their own.


Step 13: Extending the API with Custom Endpoints

You can add extra actions beyond the default CRUD operations using the @action decorator.

Example:

from rest_framework.decorators import action
from rest_framework.response import Response

class BookViewSet(viewsets.ModelViewSet):
queryset = Book.objects.all()
serializer_class = BookSerializer
@action(detail=False, methods=['get'])
def recent(self, request):
    recent_books = Book.objects.order_by('-published_date')[:5]
    serializer = self.get_serializer(recent_books, many=True)
    return Response(serializer.data)

Now the endpoint /api/books/recent/ returns the five most recently published books.


Step 14: Project Directory Recap

Your folder structure should now look like this:

library_project/
│
├── library_project/
│   ├── settings.py
│   ├── urls.py
│
├── books/
│   ├── models.py
│   ├── serializers.py
│   ├── views.py
│   ├── urls.py
│   ├── admin.py
│   ├── permissions.py
│
└── manage.py

This modular design separates concerns clearly — models handle data, serializers handle conversion, views handle logic, and routers handle routing.


Step 15: Testing the Complete API Workflow

Let’s verify the entire flow end-to-end:

  1. Register a user via the Django admin or Django’s authentication views.
  2. Obtain a token using /api/token/.
  3. Use the token in your HTTP Authorization header.
  4. Create a book with POST /api/books/.
  5. List all books with GET /api/books/.
  6. Search, filter, and order results with query parameters.
  7. Update or delete your own books only.

This workflow covers every essential operation in a secure and RESTful way.


Step 16: Advanced Enhancements

Once you’ve mastered the basics, here are ways to expand the API:

  • Pagination customization: Implement cursor or limit-offset pagination for large datasets.
  • JWT authentication: Replace token authentication with JSON Web Tokens using djangorestframework-simplejwt.
  • Throttling and rate limiting: Control how often users can access endpoints.
  • Documentation: Generate API documentation using tools like drf-yasg or drf-spectacular.
  • Testing: Write unit tests for serializers, views, and models using Django’s test framework.

Step 17: Understanding the DRF Workflow

Here’s a high-level overview of what happens when a request hits your API:

  1. URL Router: Directs the request to the appropriate ViewSet method (list, create, retrieve, update, destroy).
  2. ViewSet: Uses the queryset and serializer to process the request.
  3. Serializer: Validates and serializes data for input/output.
  4. Permissions: Check if the user is allowed to perform the action.
  5. Response: Returns serialized JSON data or error messages.

This request-response cycle ensures data integrity, validation, and security throughout.


Step 18: Why DRF Is Ideal for API Development

Django REST Framework provides several advantages:

  • Rapid development: Build full CRUD APIs with minimal code.
  • Built-in security: Permissions, authentication, and throttling are already integrated.
  • Powerful serialization: Automatic model serialization with flexible customization.
  • Browsable API: Test endpoints interactively during development.
  • Scalability: Supports large applications through modular design.

Whether you’re developing internal tools or production-grade services, DRF offers the right balance between simplicity and power.


Step 19: Troubleshooting Common Issues

  1. 401 Unauthorized:
    Ensure you’re passing the token in the header using
    Authorization: Token your_token_here.
  2. 403 Forbidden:
    Check your permissions; the user may not be allowed to modify another author’s book.
  3. Field Validation Errors:
    Ensure all required fields are included in POST or PUT requests.
  4. Filter or Search Not Working:
    Verify that django-filter is installed and listed in INSTALLED_APPS.
  5. Incorrect URLs:
    Make sure your app’s URLs are included in the project’s main urls.py.

These small checks often resolve 90% of development issues.


Comments

Leave a Reply

Your email address will not be published. Required fields are marked *