How to Document a Django REST API: A Step-by-Step Guide with DRF Spectacular & Swagger UI

By admin December 22, 2025 11 min read

How to Document a Django REST API: A Step-by-Step Guide with DRF Spectacular & Swagger UI

Building a powerful API with Django REST Framework (DRF) is only half the battle. The other crucial half is documenting it so that other developers (or your future self) can understand and use it effectively. Manually writing API docs is tedious and error-prone. Thankfully, with tools like DRF Spectacular and Swagger UI, you can generate beautiful, interactive, and always-up-to-date documentation automatically from your code.

To watch the full tutorial on YouTube, click here.

In this comprehensive tutorial, we’ll walk through building a simple book catalog API and then meticulously document it. We’ll leverage ChatGPT to help write meaningful descriptions and learn how to handle everything from basic views to complex query parameters and authentication. By the end, you’ll have a fully documented API ready for the world.

Prerequisites

Before we dive in, make sure you have a solid foundation. This tutorial assumes you have:

  • Basic knowledge of Python and Django (models, views, URLs).
  • Familiarity with Django REST Framework (DRF) concepts like serializers and viewsets.
  • Python 3.8+ and Pip installed on your system.
  • A code editor you’re comfortable with (like VS Code or PyCharm).
  • An OpenAI API key (optional, for using ChatGPT programmatically). We’ll also show manual usage.

Pro Tip: If you’re completely new to DRF, I recommend first going through the official DRF tutorial to get a feel for serializers and views.

Setting up the Development Environment

Let’s start by creating a clean, isolated environment for our project. This prevents library conflicts with other projects on your machine.

1. Create a Project Directory and Virtual Environment

Open your terminal and run the following commands:


# Create and navigate into your project directory
mkdir django_api_docs_project
cd django_api_docs_project

# Create a Python virtual environment
python3 -m venv venv

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

You should see (venv) at the beginning of your terminal line, indicating the environment is active.

2. Install Required Packages

With the virtual environment active, install Django, DRF, and our star package for documentation, DRF Spectacular.


pip install django djangorestframework drf-spectacular
    

This command installs the core libraries we need to build and document our API.

Diving into the Project

Now, let’s create our Django project and a dedicated app for our book catalog API.

1. Create Django Project and App


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

# Create an app named 'books'
python manage.py startapp books
    

2. Update Settings and Register Apps

We need to register our new books app and the third-party apps in core/settings.py.


# 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',
    'drf_spectacular',
    # Local apps
    'books',
]
    

Next, at the bottom of the same file, we configure DRF to use DRF Spectacular as its default schema generator.


# core/settings.py

REST_FRAMEWORK = {
    'DEFAULT_SCHEMA_CLASS': 'drf_spectacular.openapi.AutoSchema',
}

SPECTACULAR_SETTINGS = {
    'TITLE': 'Book Catalog API',
    'DESCRIPTION': 'A simple API to manage a collection of books.',
    'VERSION': '1.0.0',
}
    

3. Define the Book Model

In our books app, let’s define a simple Book model in books/models.py.


# books/models.py

from django.db import models

class Book(models.Model):
    title = models.CharField(max_length=200)
    author = models.CharField(max_length=100)
    publication_year = models.IntegerField()
    isbn = models.CharField(max_length=13, unique=True)
    genre = models.CharField(max_length=50, blank=True)

    def __str__(self):
        return f"{self.title} by {self.author}"
    

After creating the model, create and run the migrations.


python manage.py makemigrations
python manage.py migrate
    

Generating Seed Data using ChatGPT

To test our API, we need some data. Instead of entering it manually, we can use ChatGPT to generate a Python list of realistic book dictionaries. You can use the ChatGPT web interface or the API.

Prompt to ChatGPT: “Generate a Python list of 5 dictionaries. Each dictionary represents a book and should have the keys: ‘title’, ‘author’, ‘publication_year’, ‘isbn’, and ‘genre’. Make the data realistic for a book catalog.”

Example Output from ChatGPT:


# books/dummy_data.py

DUMMY_BOOKS = [
    {
        "title": "The Silent Patient",
        "author": "Alex Michaelides",
        "publication_year": 2019,
        "isbn": "9781250301703",
        "genre": "Psychological Thriller"
    },
    {
        "title": "Project Hail Mary",
        "author": "Andy Weir",
        "publication_year": 2021,
        "isbn": "9780593135204",
        "genre": "Science Fiction"
    },
    # ... Add 3 more books as generated
]
    

Key Takeaway: Using AI to generate seed data is a huge time-saver and produces varied, realistic data for development and testing.

Writing a View to Import Dummy Data

Let’s create a simple function-based view that will populate our database with this dummy data when we hit a specific endpoint. This is a great utility for development.

First, create a serializer for the Book model in books/serializers.py.


# books/serializers.py

from rest_framework import serializers
from .models import Book

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

Now, create the import view in books/views.py.


# books/views.py

from rest_framework.decorators import api_view
from rest_framework.response import Response
from rest_framework import status
from .models import Book
from .serializers import BookSerializer
from .dummy_data import DUMMY_BOOKS

@api_view(['POST'])
def import_dummy_data(request):
    """
    A view to import pre-defined dummy book data into the database.
    """
    books_created = []
    errors = []

    for book_data in DUMMY_BOOKS:
        serializer = BookSerializer(data=book_data)
        if serializer.is_valid():
            serializer.save()
            books_created.append(serializer.data)
        else:
            errors.append({
                "data": book_data,
                "error": serializer.errors
            })

    response_data = {
        "message": f"Import completed. Created: {len(books_created)}, Errors: {len(errors)}",
        "books_created": books_created,
        "errors": errors
    }
    return Response(response_data, status=status.HTTP_201_CREATED)
    

Finally, connect this view to a URL in books/urls.py.


# books/urls.py

from django.urls import path
from . import views

urlpatterns = [
    path('import-dummy-data/', views.import_dummy_data, name='import-dummy-data'),
]
    

Don’t forget to include this app’s URLs in the main core/urls.py.


# core/urls.py

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

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

Creating a Basic Function Based View

Let’s create a simple API endpoint to list all books. We’ll use a function-based view for clarity.


# books/views.py (add this function)

from rest_framework.decorators import api_view

@api_view(['GET'])
def book_list(request):
    """
    Retrieve a list of all books in the catalog.
    """
    books = Book.objects.all()
    serializer = BookSerializer(books, many=True)
    return Response(serializer.data)
    

Add its path in books/urls.py:


# books/urls.py

urlpatterns = [
    path('', views.book_list, name='book-list'),
    path('import-dummy-data/', views.import_dummy_data, name='import-dummy-data'),
]
    

Run your server (python manage.py runserver) and visit http://127.0.0.1:8000/api/books/. You should see an empty list or your imported data.

Setting up DRF_SPECTACULAR

Now for the magic! We’ve already added the settings. Let’s add the URLs that will serve our auto-generated schema and the beautiful Swagger UI.

Update the main core/urls.py:


# core/urls.py

from django.contrib import admin
from django.urls import path, include
from drf_spectacular.views import SpectacularAPIView, SpectacularSwaggerView

urlpatterns = [
    path('admin/', admin.site.urls),
    path('api/books/', include('books.urls')),
    # DRF Spectacular URLs
    path('api/schema/', SpectacularAPIView.as_view(), name='schema'),
    # Swagger UI
    path('api/docs/', SpectacularSwaggerView.as_view(url_name='schema'), name='swagger-ui'),
]
    

What did we just add?

  • /api/schema/: This endpoint generates the raw OpenAPI schema (a JSON/YAML file defining your entire API).
  • /api/docs/: This is the interactive Swagger UI page that reads from the schema endpoint, providing a gorgeous, clickable interface to explore and test your API.

Visit http://127.0.0.1:8000/api/docs/ right now. You’ll see the Swagger UI page with our two endpoints (/api/books/ and /api/books/import-dummy-data/) already listed! However, the descriptions are minimal. Let’s enhance them.

Writing the Docstring of a Function Based View using ChatGPT

The docstring we write in our view functions and classes is the primary source for DRF Spectacular to generate descriptions. Let’s use ChatGPT to write a comprehensive docstring for our import_dummy_data view.

Prompt to ChatGPT: “Write a detailed Google-style Python docstring for a Django REST Framework function-based view named `import_dummy_data`. The view handles a POST request, imports a list of dummy book data, and returns a summary. Include sections for Args, Returns, and Raises.”

Enhanced View with ChatGPT-Generated Docstring:


@api_view(['POST'])
def import_dummy_data(request):
    """
    Populate the database with a pre-defined set of dummy book records.

    This endpoint is designed for development and testing purposes. It iterates
    through a hard-coded list of book data (DUMMY_BOOKS), validates each entry
    using the BookSerializer, and saves valid entries to the database. It provides
    a detailed summary of the operation, including successfully created books and
    any validation errors encountered.

    Args:
        request (rest_framework.request.Request): The HTTP request object.
            No specific request data is required in the body for this endpoint.

    Returns:
        rest_framework.response.Response: A JSON response with the following structure:
            {
                "message": "Import completed. Created: X, Errors: Y",
                "books_created": [ ... ],  # List of successfully created book objects
                "errors": [ ... ]          # List of objects that failed validation with details
            }
        HTTP Status Code: 201 (Created) on successful processing of the request.

    Raises:
        ValidationError: If any of the serializers fail during validation (handled internally
                        and reported in the 'errors' list).
    """
    # ... (the rest of the function code remains the same)
    

Refresh your Swagger UI page (/api/docs/). Click on the “POST /api/books/import-dummy-data/” endpoint. You’ll now see the rich description, arguments, and response structure pulled directly from the docstring! This makes your API instantly more understandable.

Documenting a Class Based View – ModelViewset

For managing full CRUD (Create, Read, Update, Delete) operations on books, a ModelViewSet is more efficient. Let’s create one and document it.

Create a new viewset in books/views.py:


# books/views.py

from rest_framework import viewsets

class BookViewSet(viewsets.ModelViewSet):
    """
    API endpoint that allows books to be viewed, created, edited, or deleted.
    """
    queryset = Book.objects.all()
    serializer_class = BookSerializer
    

Register this viewset with a router in books/urls.py for automatic URL generation.


# books/urls.py

from rest_framework.routers import DefaultRouter
from .views import BookViewSet

router = DefaultRouter()
router.register(r'', BookViewSet, basename='book')

urlpatterns = [
    path('', views.book_list, name='book-list'),
    path('import-dummy-data/', views.import_dummy_data, name='import-dummy-data'),
]

urlpatterns += router.urls
    

The router automatically creates endpoints like /api/books/ (GET, POST), /api/books/<id>/ (GET, PUT, PATCH, DELETE). Check Swagger UI again, and you’ll see all these new endpoints listed under “book”.

How to add query parameters to your API endpoints.

Often, you need to filter results. Let’s add the ability to filter books by author and publication_year via query parameters like /api/books/?author=Andy+Weir.

We modify the BookViewSet to override the get_queryset method.


class BookViewSet(viewsets.ModelViewSet):
    """
    API endpoint that allows books to be viewed, created, edited, or deleted.
    Provides basic filtering by 'author' and 'publication_year' via query parameters.
    """
    serializer_class = BookSerializer

    def get_queryset(self):
        """
        Optionally filters the returned books by 'author' or 'publication_year'
        present in the query parameters.
        """
        queryset = Book.objects.all()
        author = self.request.query_params.get('author')
        publication_year = self.request.query_params.get('publication_year')

        if author:
            queryset = queryset.filter(author__icontains=author)
        if publication_year:
            queryset = queryset.filter(publication_year=publication_year)
        return queryset
    

To document these parameters for Swagger, we use the @extend_schema decorator provided by DRF Spectacular.


from drf_spectacular.utils import extend_schema, OpenApiParameter

class BookViewSet(viewsets.ModelViewSet):
    serializer_class = BookSerializer

    @extend_schema(
        parameters=[
            OpenApiParameter(
                name='author',
                description='Filter books by author name (case-insensitive, partial match).',
                required=False,
                type=str,
            ),
            OpenApiParameter(
                name='publication_year',
                description='Filter books by exact publication year.',
                required=False,
                type=int,
            ),
        ]
    )
    def list(self, request, *args, **kwargs):
        return super().list(request, *args, **kwargs)

    def get_queryset(self):
        # ... filtering logic remains the same ...
    

Now, in Swagger UI, when you expand the GET /api/books/ endpoint, you’ll see input fields for the author and publication_year query parameters with their descriptions.

Generating the Docstring of the ModelViewset Methods using ChatGPT

We can use ChatGPT to generate detailed docstrings for all the methods in our ModelViewSet (list, create, retrieve, update, destroy). This provides granular documentation for each operation.

Prompt to ChatGPT: “Generate Google-style docstrings for the five main methods of a Django REST Framework ModelViewSet (list, create, retrieve, update, destroy) for a ‘Book’ model. Include Args and Returns for each.”

You can then add these generated docstrings to your viewset methods, making your API documentation incredibly thorough.

Handling Authenticated Requests

Most real-world APIs require authentication. Let’s secure our BookViewSet so that only authenticated users can create, update, or delete books, while anyone can read them.

First, set the default permission and authentication classes in core/settings.py.


# core/settings.py

REST_FRAMEWORK = {
    'DEFAULT_SCHEMA_CLASS': 'drf_spectacular.openapi.AutoSchema',
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework.authentication.SessionAuthentication',
        'rest_framework.authentication.BasicAuthentication',
    ],
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.IsAuthenticatedOrReadOnly',
    ],
}
    

This configuration allows read-only access to unauthenticated users but requires authentication for write operations.

To document this in Swagger, we need to tell it about our authentication schemes. Update the SPECTACULAR_SETTINGS:


# core/settings.py

SPECTACULAR_SETTINGS = {
    'TITLE': 'Book Catalog API',
    'DESCRIPTION': 'A simple API to manage a collection of books. Requires authentication for POST, PUT, PATCH, DELETE.',
    'VERSION': '1.0.0',
    # Add this section:
    'SERVE_INCLUDE_SCHEMA': False,
    'COMPONENT_SPLIT_REQUEST': True,
    'SECURITY': [{'basicAuth': []}],
    'SERVERS': [{'url': 'http://127.0.0.1:8000', 'description': 'Local server'}],
}
    

Now, you’ll see an “Authorize” button at the top of your Swagger UI page. Clicking it lets you input basic auth credentials (username/password of a Django user you’ve created) to test the protected endpoints directly from the documentation.

To watch the full tutorial on YouTube, click here.

Final Note: You’ve successfully built a documented, functional, and secure Django REST API. The combination of DRF Spectacular for automatic schema generation and Swagger UI for presentation is incredibly powerful. Using ChatGPT to craft meaningful descriptions accelerates the process and improves quality, ensuring your API is a pleasure for other developers to use.

Remember, great documentation is not a luxury; it’s a necessity for any API that hopes to be adopted. Happy coding, and keep building amazing things!

Watch The Video

Watch on YouTube

How did this article make you feel?

Share This Post

About admin

Founder and Lead Developer at Halogenius Ideas, bridging the gap between professional web design and accessible tech education. With years of experience in full-stack development and a passion for teaching, I lead a team dedicated to building stunning digital experiences while empowering the next generation of developers through comprehensive tutorials and courses. When I'm not coding or creating content, you'll find me exploring new technologies and mentoring aspiring developers.

Leave a Reply

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

4 + five =
Powered by MathCaptcha