Hussini
Published on

How to Add Interactive Swagger Documentation with API Versioning to a Django Rest Framework API

Authors

Introduction

An API is like a contract between a provider and a consumer. Adding versioning to the API makes sure that consumers know when things change or break.

While an interactive documentation makes it easier to interact with the API in a web browser.

In this article, I'm going to teach you every thing you need to know in order to start adding documentation and versioning to your DRF API.

Pre-requisite

To follow along with this tutorial, basic knowledge of building RESTful APIs with Django Rest Framework is required.

Swagger Documentation

We are going to start with adding swagger documentation to our DRF API. I created a sample project you can use to follow along if you don't have one.

Installing Dependencies

We'll be using drf-spectacular, a django package that makes it easier to add swagger documentation and automatically generates json and yaml schema.

pip install drf-spectacular

Add drf_spectacular to INSTALLED_APPS list

settings.py
INSTALLED_APPS = [
    # ALL YOUR APPS
    'drf_spectacular',
]

Set DRF DEFAULT_SCHEMA_CLASS to drf_spectacular.openapi.AutoSchema

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

drf-spectacular comes pre-configured with some default settings, so you don't need to specify any settings before it works, but we are going customize it fit our use case. Have a look at the available settings.

settings.py
SPECTACULAR_SETTINGS = {
    'TITLE': 'My DRF API',
    'DESCRIPTION': 'My Spectacular API',
    'VERSION': '1.0.0',
    'SERVE_INCLUDE_SCHEMA': False,
    # OTHER SETTINGS
}

Usage

Add drf_spectacular views to your project's urls.py.

project/urls.py
from django.urls import path, include
from drf_spectacular.views import SpectacularSwaggerView, SpectacularJSONAPIView

urlpatterns = [
    # OTHER PATHS
    path("schema.json", SpectacularJSONAPIView.as_view(), name="schema"),
    path(
        "docs/",
        SpectacularSwaggerView.as_view(url_name="schema"),
        name="swagger-ui",
    ),
]

Now when you visit /docs page, you will see a generated swagger docs that is based on your seralizers.

Swagger Docs

An OpenAPI JSON schema is also available at /schema.json, which you can use for multiple purposes, one of which is generating SDKs to interact with your API in almost any programming language.

If you're unable to access the docs, you'll need to install drf-spectacular[sidecar], you can read more about why you have to do that here.

pip install drf-spectacular[sidecar]
settings.py
INSTALLED_APPS = [
    # ALL YOUR APPS
    'drf_spectacular',
    'drf_spectacular_sidecar',
]

SPECTACULAR_SETTINGS = {
    "TITLE": "My DRF API",
    "DESCRIPTION": "My Spectacular API",
    "VERSION": "1.0.0",
    "SERVE_INCLUDE_SCHEMA": False,
    "SWAGGER_UI_DIST": "SIDECAR",
    "SWAGGER_UI_FAVICON_HREF": "SIDECAR",
    "REDOC_DIST": "SIDECAR",
}

Customization

There are multiple ways you can customize the generated schema for an endpoint, but we will focus on using the extend_schema_view and extend_schema decorators. You can read learn more about customization here.

The following code will add description to our /smartphones/ endpoint.

views.py
from .serializers import PhoneSerializer
from .models import Phone
from rest_framework.viewsets import GenericViewSet
from rest_framework.mixins import ListModelMixin
from drf_spectacular.utils import extend_schema_view, extend_schema


@extend_schema_view(list=extend_schema(description="List of smartphones."))
class PhoneViewSet(GenericViewSet, ListModelMixin):
    queryset = Phone.objects.all()
    serializer_class = PhoneSerializer

You can also use extend_schema to customize individual view methods.

views.py
class PhoneViewSet(GenericViewSet, ListModelMixin):
    queryset = Phone.objects.all()
    serializer_class = PhoneSerializer

    @extend_schema(description="List of smartphones.")
    def list(self, request, *args, **kwargs):
        return super().list(request, *args, **kwargs)

Versioning

Django Rest Framework provides out of the box solution for adding versioning to an API, and there are 5 different ways you can set it up, which are referred to as 'versioning schemes' in the official documentation.

The 5 versioning schemes are:

1. URLPathVersioning

Expects the version to be include as part of the URL path.

GET /v1/smartphones/ HTTP/1.1
Host: myapi.com
Accept: application/json

2. NamespaceVersioning

Thesame as URLPathVersioning, the only difference is how it is configured using namespace.

3. AcceptHeaderVersioning

Expects the client to specify the version in the Accept header.

GET /smartphones/ HTTP/1.1
Host: myapi.com
Accept: application/json; version=1.0

4. HostNameVersioning

Expects the client to specify the version as part of the hostname in the URL.

GET /smartphones/ HTTP/1.1
Host: v1.myapi.com
Accept: application/json

5. QueryParameterVersioning

Expects the client to specify the version as a query parameter in the URL path.

GET /smartphones/?version=1.0 HTTP/1.1
Host: myapi.com
Accept: application/json

In this tutorial we are going to show you how to setup URLPathVersioning, which I believe is the easiest to setup.

Configuration & Usage

settings.py
REST_FRAMEWORK = {
    # OTHER SETTINGS
    "DEFAULT_VERSIONING_CLASS": "rest_framework.versioning.URLPathVersioning",
    "DEFAULT_VERSION": "v1",
    "ALLOWED_VERSIONS": ["v1"],
}
project/urls.py
urlpatterns = [
    # OTHER URLS
    path("v1/smartphones/", include("smartphones.urls")),
]

When you visit /v1/smartphones/, it'll return a list of smartphones just like before but with the version included in the path.

Now let's see how to add another version of our API, we'll call it v2.

To achieve that, we are going to do the following:

  1. Create a new ViewSet which we're going to use for our /v2/smartphones/ endpoints.
  2. Rename smartphones/urls.py to smartphones/v1_urls.py.
  3. Create a new file smartphones/v2_urls.py and add paths to it.
  4. Add v2 to ALLOWED_VERSIONS list.
smartphones/views.py
from .serializers import PhoneSerializer
from .models import Phone
from rest_framework.viewsets import GenericViewSet
from rest_framework.mixins import ListModelMixin, RetrieveModelMixin
from drf_spectacular.utils import extend_schema_view, extend_schema


@extend_schema_view(list=extend_schema(description="List of smartphones."))
class PhoneViewSet(GenericViewSet, ListModelMixin):
    queryset = Phone.objects.all()
    serializer_class = PhoneSerializer


class PhoneV2ViewSet(GenericViewSet, ListModelMixin, RetrieveModelMixin):
    queryset = Phone.objects.all()
    serializer_class = PhoneSerializer

    @extend_schema(description="List of smartphones.")
    def list(self, request, *args, **kwargs):
        return super().list(request, *args, **kwargs)

    @extend_schema(description="Retrieve a smartphone.")
    def retrieve(self, request, *args, **kwargs):
        return super().retrieve(request, *args, **kwargs)
smartphones/v2_urls.py
from rest_framework.routers import DefaultRouter
from .views import PhoneV2ViewSet
from django.urls import path, include

router = DefaultRouter()

router.register("", PhoneV2ViewSet, basename="phones")

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

Then include it in your project's project/urls.py:

project/urls.py

urlpatterns = [
    # OTHER URLS
    path("v1/smartphones/", include("smartphones.v1_urls")),
    path("v2/smartphones/", include("smartphones.v2_urls")),
]
settings.py
REST_FRAMEWORK = {
    # OTHER SETTINGS
    "ALLOWED_VERSIONS": ["v1", "v2"],
}

Now when you visit the docs page, you'll see 2 new smartphone endpoints grouped together under v2.

Swagger docs versioned

What if we want to group our endpoints based on the respective apps they belong to, instead of the version string? All we have to do is add SCHEMA_PATH_PREFIX to drf-spectacular settings.

settings.py
SPECTACULAR_SETTINGS = {
    # Other Settings
    "SCHEMA_PATH_PREFIX": "/v(?P<version>[0-9]+)/",
}
Swagger docs grouped by app

That's how you add interactive docs with versioning to your DRF API.

Source code for the final project is available on github.