How to Document a Django REST API: A Step-by-Step Guide with DRF Spectacular & Swagger UI
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!
Leave a Reply