Paramclasses Academy - Blog

Tutorials Details

←
Post 29 of 29

Complete Django REST Framework Project – Building, Testing, Securing, and Deploying a Real-World REST API

Complete Django REST Framework Project – Building, Testing, Securing, and Deploying a Real-World REST API

October 21, 2026

Complete Django REST Framework Project: Building, Testing, Securing, and Deploying a Real-World REST API

Introduction

Welcome to Module 30, the final module in this Django course series. In the previous modules, you learned about Django fundamentals, models, ORM queries, forms, authentication, class-based views, REST APIs, automated testing, production deployment, and performance optimization.

In this module, you will combine those concepts to build a practical Product Management REST API using Django and Django REST Framework.

The project will include product and category management, serializers, CRUD endpoints, authentication, permissions, filtering, pagination, automated testing, API documentation, and production configuration.

The goal is to understand how individual Django components work together in a structured application that can be extended for real-world requirements.

What You Will Learn

  • How to organize a complete Django REST Framework project.

  • How to create models and serializers.

  • How to build CRUD endpoints using ModelViewSet.

  • How to configure URL routing with a DRF router.

  • How to implement authentication and permissions.

  • How to add filtering, searching, ordering, and pagination.

  • How to write automated API tests.

  • How to generate OpenAPI documentation and Swagger UI.

  • How to improve database query performance.

  • How to prepare the project for production deployment.

1. Project Overview

We will build a Product Management API that allows authorized users to manage products and categories.

Main Features

  1. Create, view, update, and delete products.

  2. Organize products into categories.

  3. Validate incoming request data.

  4. Restrict write operations to authorized users.

  5. Search and filter product records.

  6. Paginate large product lists.

  7. Test API endpoints automatically.

  8. Generate interactive API documentation.

  9. Optimize database queries.

  10. Configure production-oriented settings.

Suggested Project Structure

django_api_project/
│
├── manage.py
├── requirements.txt
├── .env
│
├── config/
│   ├── __init__.py
│   ├── settings.py
│   ├── urls.py
│   ├── wsgi.py
│   └── asgi.py
│
└── products/
    ├── __init__.py
    ├── admin.py
    ├── apps.py
    ├── models.py
    ├── serializers.py
    ├── views.py
    ├── urls.py
    └── tests.py

Your actual project package name may differ. Keep the imports and URL configuration consistent with your own project.

2. Creating the Django Project

Open a terminal and create a virtual environment.

python -m venv venv

Activate it on Windows:

venv\Scripts\activate

Install Django, Django REST Framework, and the API documentation package:

pip install django djangorestframework drf-spectacular django-filter

Create the project and application:

django-admin startproject config .
python manage.py startapp products

Add the applications to INSTALLED_APPS in config/settings.py:

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

    "rest_framework",
    "drf_spectacular",
    "django_filters",

    "products",
]

This configuration enables Django's built-in applications, Django REST Framework, API documentation, filtering, and the product application.

3. Creating the Database Models

Models define the structure of your application's database.

Create two models in products/models.py.

from django.db import models


class Category(models.Model):
    name = models.CharField(
        max_length=100,
        unique=True
    )
    description = models.TextField(
        blank=True
    )

    def __str__(self):
        return self.name


class Product(models.Model):
    category = models.ForeignKey(
        Category,
        on_delete=models.PROTECT,
        related_name="products"
    )
    name = models.CharField(max_length=150)
    description = models.TextField(blank=True)
    price = models.DecimalField(
        max_digits=10,
        decimal_places=2
    )
    stock = models.PositiveIntegerField(default=0)
    created_at = models.DateTimeField(
        auto_now_add=True
    )

    class Meta:
        ordering = ["-created_at"]
        indexes = [
            models.Index(fields=["name"]),
        ]

    def __str__(self):
        return self.name

Understanding the Models

  • Category stores product category information.

  • Product stores product details and inventory quantities.

  • ForeignKey connects each product to a category.

  • on_delete=models.PROTECT prevents a category from being deleted while products still reference it.

  • DecimalField stores prices using decimal precision rather than binary floating-point values.

  • indexes defines a database index that can help suitable queries on product names.

  • created_at records when a product is created.

Create and apply the database migrations:

python manage.py makemigrations
python manage.py migrate

4. Creating Serializers

A serializer converts model instances into API response data and validates incoming data before it is saved.

Create products/serializers.py.

from rest_framework import serializers
from .models import Category, Product


class CategorySerializer(serializers.ModelSerializer):

    class Meta:
        model = Category
        fields = [
            "id",
            "name",
            "description",
        ]


class ProductSerializer(serializers.ModelSerializer):
    category_name = serializers.CharField(
        source="category.name",
        read_only=True
    )

    class Meta:
        model = Product
        fields = [
            "id",
            "category",
            "category_name",
            "name",
            "description",
            "price",
            "stock",
            "created_at",
        ]
        read_only_fields = [
            "id",
            "created_at",
        ]

Why Are Serializers Important?

Serializers help developers:

  • Validate request data.

  • Convert model objects into JSON-compatible representations.

  • Control which fields are exposed by an API.

  • Prevent clients from directly modifying read-only fields.

In this example, clients can submit a category ID through the category field, while category_name is returned as read-only information.

5. Building API Views

Django REST Framework provides ModelViewSet for common model-based CRUD operations.

Create products/views.py.

from rest_framework.viewsets import ModelViewSet
from rest_framework.permissions import (
    IsAuthenticatedOrReadOnly,
)

from .models import Category, Product
from .serializers import (
    CategorySerializer,
    ProductSerializer,
)


class CategoryViewSet(ModelViewSet):
    queryset = Category.objects.all()
    serializer_class = CategorySerializer
    permission_classes = [
        IsAuthenticatedOrReadOnly
    ]


class ProductViewSet(ModelViewSet):
    queryset = Product.objects.select_related(
        "category"
    )
    serializer_class = ProductSerializer
    permission_classes = [
        IsAuthenticatedOrReadOnly
    ]

Understanding ModelViewSet

ModelViewSet provides actions for:

  • Listing records.

  • Retrieving individual records.

  • Creating records.

  • Updating records.

  • Partially updating records.

  • Deleting records.

The select_related("category") expression retrieves category information efficiently for product responses.

The IsAuthenticatedOrReadOnly permission allows read-only access to unauthenticated users while requiring authentication for write operations. For a production application, you may need stricter permissions based on roles, ownership, and business rules.

6. Configuring URL Routing

Create products/urls.py.

from django.urls import path, include
from rest_framework.routers import DefaultRouter

from .views import CategoryViewSet, ProductViewSet


router = DefaultRouter()

router.register(
    "categories",
    CategoryViewSet,
    basename="category"
)

router.register(
    "products",
    ProductViewSet,
    basename="product"
)

urlpatterns = [
    path("", include(router.urls)),
]

Now configure config/urls.py.

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

from drf_spectacular.views import (
    SpectacularAPIView,
    SpectacularSwaggerView,
    SpectacularRedocView,
)


urlpatterns = [
    path("admin/", admin.site.urls),

    path("api/", include("products.urls")),

    path(
        "api/schema/",
        SpectacularAPIView.as_view(),
        name="schema"
    ),

    path(
        "api/docs/",
        SpectacularSwaggerView.as_view(
            url_name="schema"
        ),
        name="swagger-ui"
    ),

    path(
        "api/redoc/",
        SpectacularRedocView.as_view(
            url_name="schema"
        ),
        name="redoc"
    ),
]

The router generates the standard API routes automatically.

7. Adding Search, Filtering, and Ordering

Django REST Framework supports filtering and searching through configurable filter backends.

Add the following configuration to config/settings.py:

REST_FRAMEWORK = {
    "DEFAULT_SCHEMA_CLASS":
        "drf_spectacular.openapi.AutoSchema",

    "DEFAULT_FILTER_BACKENDS": [
        "django_filters.rest_framework.DjangoFilterBackend",
        "rest_framework.filters.SearchFilter",
        "rest_framework.filters.OrderingFilter",
    ],

    "DEFAULT_PAGINATION_CLASS":
        "rest_framework.pagination.PageNumberPagination",

    "PAGE_SIZE": 10,
}

If you already have a REST_FRAMEWORK dictionary, merge these settings with the existing authentication, permission, and other configuration rather than overwriting it.

Update ProductViewSet:

from rest_framework.filters import (
    SearchFilter,
    OrderingFilter,
)
from django_filters.rest_framework import (
    DjangoFilterBackend,
)


class ProductViewSet(ModelViewSet):
    queryset = Product.objects.select_related(
        "category"
    )
    serializer_class = ProductSerializer

    permission_classes = [
        IsAuthenticatedOrReadOnly
    ]

    filter_backends = [
        DjangoFilterBackend,
        SearchFilter,
        OrderingFilter,
    ]

    filterset_fields = [
        "category",
        "stock",
    ]

    search_fields = [
        "name",
        "description",
    ]

    ordering_fields = [
        "name",
        "price",
        "created_at",
    ]

    ordering = ["-created_at"]

Example API Requests

Retrieve products:

GET /api/products/

Search by product name or description:

GET /api/products/?search=keyboard

Filter by category ID:

GET /api/products/?category=1

Order products by price:

GET /api/products/?ordering=price

Retrieve the second page:

GET /api/products/?page=2

These features allow clients to retrieve manageable, relevant datasets without downloading every record.

8. Configuring Authentication

For this example, use Django REST Framework's built-in token authentication.

Add the token application:

INSTALLED_APPS = [
    # Existing applications...
    "rest_framework.authtoken",
]

Apply migrations:

python manage.py migrate

Update the REST_FRAMEWORK configuration:

REST_FRAMEWORK = {
    "DEFAULT_AUTHENTICATION_CLASSES": [
        "rest_framework.authentication.TokenAuthentication",
        "rest_framework.authentication.SessionAuthentication",
    ],

    "DEFAULT_PERMISSION_CLASSES": [
        "rest_framework.permissions.IsAuthenticatedOrReadOnly",
    ],

    "DEFAULT_SCHEMA_CLASS":
        "drf_spectacular.openapi.AutoSchema",

    "DEFAULT_PAGINATION_CLASS":
        "rest_framework.pagination.PageNumberPagination",

    "PAGE_SIZE": 10,

    "DEFAULT_FILTER_BACKENDS": [
        "django_filters.rest_framework.DjangoFilterBackend",
        "rest_framework.filters.SearchFilter",
        "rest_framework.filters.OrderingFilter",
    ],
}

This is a combined configuration example. Keep any other settings required by your project.

Add a token endpoint to config/urls.py:

from rest_framework.authtoken.views import obtain_auth_token

urlpatterns = [
    # Existing URL patterns...
    path(
        "api/token/",
        obtain_auth_token,
        name="api-token"
    ),
]

A user with valid credentials can request a token from this endpoint. An authenticated client can then send it using the HTTP header:

Authorization: Token YOUR_TOKEN_HERE

Security note: Use HTTPS in production. Tokens must be protected like passwords, and production applications should consider token revocation, expiration requirements, and stronger authorization policies where appropriate.

9. Adding API Documentation

Add the following configuration to config/settings.py:

SPECTACULAR_SETTINGS = {
    "TITLE": "Product Management API",
    "DESCRIPTION": (
        "API for managing products and categories."
    ),
    "VERSION": "1.0.0",
}

Start the development server:

python manage.py runserver

Open the interactive documentation:

http://127.0.0.1:8000/api/docs/

The schema endpoint is available at:

http://127.0.0.1:8000/api/schema/

Swagger UI allows developers to inspect endpoint methods, request parameters, schemas, and response formats. Actual requests still depend on authentication, permissions, and the data available in the database.

10. Writing Automated API Tests

Create tests in products/tests.py.

from rest_framework import status
from rest_framework.test import APITestCase

from .models import Category, Product


class ProductAPITest(APITestCase):

    def setUp(self):
        self.category = Category.objects.create(
            name="Electronics",
            description="Electronic products"
        )

        self.product = Product.objects.create(
            category=self.category,
            name="Keyboard",
            description="USB keyboard",
            price="1500.00",
            stock=10
        )

        self.list_url = "/api/products/"
        self.detail_url = (
            f"/api/products/{self.product.id}/"
        )

    def test_product_list(self):
        response = self.client.get(
            self.list_url
        )

        self.assertEqual(
            response.status_code,
            status.HTTP_200_OK
        )

        self.assertEqual(
            response.data["count"],
            1
        )

    def test_product_detail(self):
        response = self.client.get(
            self.detail_url
        )

        self.assertEqual(
            response.status_code,
            status.HTTP_200_OK
        )

        self.assertEqual(
            response.data["name"],
            "Keyboard"
        )

    def test_unauthenticated_user_cannot_create_product(
        self
    ):
        payload = {
            "category": self.category.id,
            "name": "Mouse",
            "description": "Wireless mouse",
            "price": "700.00",
            "stock": 20,
        }

        response = self.client.post(
            self.list_url,
            payload,
            format="json"
        )

        self.assertEqual(
            response.status_code,
            status.HTTP_401_UNAUTHORIZED
        )

Understanding the Tests

The first test verifies the paginated product list. Because pagination is configured, the response includes a count field.

The second test verifies that an individual product can be retrieved.

The third test verifies that an unauthenticated request cannot create a product when token authentication is configured as shown. Authentication responses can vary with the authentication configuration, so update the expected status if your project's configured authentication classes behave differently.

Run the tests:

python manage.py test products

A complete project should also test successful authenticated creation, updates, deletion, invalid input, filtering, and permission boundaries.

11. Optimizing Database Queries

Efficient database access is essential for production APIs.

The Product API already uses:

Product.objects.select_related("category")

This avoids repeated queries when the serializer accesses category details.

Other optimization techniques include:

  • Use prefetch_related() for reverse and many-to-many relationships.

  • Add indexes for appropriate query patterns.

  • Use pagination for large result sets.

  • Avoid retrieving unused fields where doing so is beneficial.

  • Inspect database query plans before adding indexes.

  • Measure query counts and response times before and after changes.

Optimization should be based on actual usage and measurements rather than assumptions.

12. Configuring Production Settings

A production application should not hardcode secret keys or database passwords.

A simple environment-based configuration can read values using Python's os module:

import os

SECRET_KEY = os.environ["DJANGO_SECRET_KEY"]

DEBUG = (
    os.environ.get("DJANGO_DEBUG", "False").lower()
    == "true"
)

ALLOWED_HOSTS = [
    host.strip()
    for host in os.environ.get(
        "DJANGO_ALLOWED_HOSTS", ""
    ).split(",")
    if host.strip()
]

For production, supply the environment variables through the hosting platform or a secrets manager.

Also verify:

  • HTTPS and secure cookie settings.

  • Database access permissions.

  • Static-file configuration.

  • Application logging.

  • Dependency versions.

  • API authentication and authorization.

  • Database backups and recovery procedures.

Run Django's deployment checks:

python manage.py check --deploy

Review the output and resolve relevant warnings for your deployment environment.

13. Running the Complete Project

Follow this sequence when testing the project locally.

Step 1: Activate your virtual environment.

venv\Scripts\activate

Step 2: Apply migrations.

python manage.py migrate

Step 3: Create an administrator account if required.

python manage.py createsuperuser

Step 4: Run automated tests.

python manage.py test

Step 5: Start the development server.

python manage.py runserver

Step 6: Open the API endpoints and documentation.

http://127.0.0.1:8000/api/products/
http://127.0.0.1:8000/api/categories/
http://127.0.0.1:8000/api/docs/

Use a suitable API client or Swagger UI to test requests, verify responses, and check authentication requirements.

Remember that the Django development server is for development and testing, not production deployment.

14. Final Project Checklist

  • Django project and application are configured.

  • Product and Category models are created.

  • Migrations are applied.

  • Serializers validate and represent data correctly.

  • CRUD endpoints are available.

  • URL routing is configured.

  • Authentication and permissions are tested.

  • Filtering, searching, and ordering work.

  • Pagination is configured.

  • Database queries are reviewed.

  • Automated API tests pass.

  • Swagger UI and OpenAPI documentation work.

  • Production secrets are stored securely.

  • Deployment settings have been reviewed.

  • Database backups and logging are planned.

Frequently Asked Questions (FAQs)

Q1. What is Django REST Framework?

Django REST Framework is a toolkit built on Django for creating REST APIs with serializers, views, authentication, permissions, and other API features.

Q2. What is the purpose of a serializer?

A serializer converts complex data such as model instances into API-compatible representations and validates incoming data.

Q3. Why use ModelViewSet?

ModelViewSet provides standard CRUD actions for model-based APIs, reducing repetitive view code.

Q4. What is the purpose of a router?

A DRF router automatically generates URL patterns for registered viewsets.

Q5. Why are permissions important?

Permissions determine which users can read or modify resources and help prevent unauthorized operations.

Q6. What is pagination?

Pagination divides a large collection of records into smaller responses that can be retrieved page by page.

Q7. Why should API endpoints be tested?

Automated tests verify expected behavior and help identify regressions when the application changes.

Q8. What is Swagger UI?

Swagger UI displays API documentation generated from an API schema and can provide an interactive way to explore endpoints.

Q9. Why optimize database queries?

Efficient queries can reduce database load and improve response times, especially when APIs handle many records or concurrent requests.

Q10. What should be checked before deployment?

Review security settings, credentials, permissions, database migrations, dependencies, logging, backups, static files, and deployment checks.

Interview Questions and Answers

1. What is the difference between Django and Django REST Framework?

Django is a general-purpose web framework. Django REST Framework builds on Django to provide tools specifically for designing and implementing APIs.

2. What is the difference between a serializer and a model?

A model describes database-backed data and its relationships. A serializer controls how data is represented and validated when exchanged through an API.

3. What is the difference between authentication and authorization?

Authentication establishes who a user is. Authorization determines what that user is allowed to do.

4. What is the purpose of select_related()?

It retrieves related objects through SQL joins and is particularly useful for foreign-key and one-to-one relationships.

5. Why is pagination useful in REST APIs?

Pagination limits the number of records returned in one response, helping manage data transfer, memory usage, and processing costs.

6. What is an HTTP 401 response?

HTTP 401 indicates that valid authentication credentials are missing or not accepted, depending on the authentication scheme.

7. What is an HTTP 403 response?

HTTP 403 indicates that the server refuses the requested operation, commonly because the request does not have the necessary permission.

8. What is OpenAPI?

OpenAPI is a specification for describing API endpoints, request parameters, response schemas, and security requirements in a structured format.

9. Why should production secrets be stored outside source code?

It reduces the risk of exposing credentials through version control, shared files, or accidental source-code disclosure.

10. What makes a REST API production-ready?

A production-ready API requires reliable endpoint behavior, correct permissions, validation, testing, monitoring, secure configuration, performance review, and a suitable deployment process.

Practical Assignment

Build and demonstrate the complete Product Management REST API.

Required tasks:

  1. Create a Django project and a products application.

  2. Build Product and Category models.

  3. Implement serializers and model viewsets.

  4. Register API routes with a DRF router.

  5. Configure token authentication and permissions.

  6. Implement filtering, searching, ordering, and pagination.

  7. Generate OpenAPI documentation with Swagger UI.

  8. Write automated tests for CRUD operations and access control.

  9. Optimize related-object queries.

  10. Configure environment-based production settings.

  11. Run the test suite and Django deployment checks.

  12. Document the API endpoints and deployment process.

Expected Result: A structured, tested Django REST Framework project that demonstrates the main concepts covered throughout this 30-module course and provides a foundation for further real-world development.

Final Conclusion

Congratulations on completing all 30 modules of the Django course series!

Throughout this course, you explored Django project setup, models, ORM, forms, authentication, class-based views, REST API development, CRUD operations, permissions, filtering, testing, documentation, deployment, caching, and performance optimization.

The most important next step is to apply these concepts in a project of your own. Build a practical application, test its behavior, document its endpoints, review its security, and deploy it using an appropriate production environment.

Continue learning by exploring advanced API permissions, background tasks, automated deployment pipelines, monitoring, and scalable application architecture.

Course Complete: Django Fundamentals to Django REST Framework API Development.

←
Post 29 of 29