Skip to main content

Django REST Versioning

Introduction

API versioning is a crucial aspect of maintaining web services over time. As your API evolves, you'll inevitably need to make changes that could break existing client applications. Django REST Framework provides several versioning schemes to help you implement versioning in a consistent way, allowing you to introduce changes while maintaining backward compatibility.

In this guide, we'll explore:

  • Why API versioning is important
  • Different versioning schemes in DRF
  • How to implement versioning in your Django REST API
  • Best practices for maintaining versioned APIs

Why Version Your API?

Before diving into implementation details, let's understand why versioning is important:

  1. Backward Compatibility: Allows existing clients to continue working even as your API evolves
  2. Progressive Enhancement: Lets you add new features without breaking existing functionality
  3. Deprecation Strategy: Provides a clear path for phasing out older API versions
  4. Client Migration: Gives client developers time to update their applications

Versioning Schemes in Django REST Framework

Django REST Framework supports several versioning schemes out of the box:

1. URL Path Versioning

This scheme includes the version as part of the URL path:

http://example.com/api/v1/users/
http://example.com/api/v2/users/

2. Query Parameter Versioning

Adds the version as a query parameter:

http://example.com/api/users/?version=v1
http://example.com/api/users/?version=v2

3. Accept Header Versioning

Uses the HTTP Accept header to specify the version:

Accept: application/json; version=v1
Accept: application/json; version=v2

4. Hostname Versioning

Specifies the version in the hostname:

http://v1.example.com/api/users/
http://v2.example.com/api/users/

5. Content-Type Header Versioning

Includes the version in the content-type header:

Content-Type: application/json; version=v1

Setting Up Versioning in Django REST Framework

Step 1: Configure the Default Versioning Scheme

In your settings.py file, add the following configuration:

python
REST_FRAMEWORK = {
'DEFAULT_VERSIONING_CLASS': 'rest_framework.versioning.URLPathVersioning',
'DEFAULT_VERSION': 'v1',
'ALLOWED_VERSIONS': ['v1', 'v2'],
'VERSION_PARAM': 'version', # The name of the parameter to use (for query param and headers)
}

Step 2: Update URL Patterns (for URL Path Versioning)

If using URL path versioning, update your URLs:

python
# urls.py
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from myapp import views

router_v1 = DefaultRouter()
router_v1.register(r'users', views.UserViewSetV1)

router_v2 = DefaultRouter()
router_v2.register(r'users', views.UserViewSetV2)

urlpatterns = [
path('api/v1/', include(router_v1.urls)),
path('api/v2/', include(router_v2.urls)),
]

Step 3: Create Version-Specific ViewSets

Create different ViewSets for different versions:

python
# views.py
from rest_framework import viewsets
from .models import User
from .serializers import UserSerializerV1, UserSerializerV2

class UserViewSetV1(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializerV1

class UserViewSetV2(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializerV2

Step 4: Create Version-Specific Serializers

python
# serializers.py
from rest_framework import serializers
from .models import User

class UserSerializerV1(serializers.ModelSerializer):
class Meta:
model = User
fields = ['id', 'username', 'email']

class UserSerializerV2(serializers.ModelSerializer):
full_name = serializers.SerializerMethodField()

class Meta:
model = User
fields = ['id', 'username', 'email', 'full_name', 'created_at']

def get_full_name(self, obj):
return f"{obj.first_name} {obj.last_name}"

Version-Based Logic within a Single ViewSet

You can also handle versioning within a single ViewSet by checking the requested version:

python
from rest_framework import viewsets

class UserViewSet(viewsets.ModelViewSet):
queryset = User.objects.all()

def get_serializer_class(self):
if self.request.version == 'v1':
return UserSerializerV1
return UserSerializerV2

def list(self, request, *args, **kwargs):
# Access the version with request.version
if request.version == 'v1':
# v1 specific logic
pass
else:
# v2 specific logic
pass
return super().list(request, *args, **kwargs)

Practical Example: Building a Versioned Blog API

Let's build a versioned API for a blog application:

  1. First, define your models:
python
# models.py
from django.db import models

class Post(models.Model):
title = models.CharField(max_length=100)
content = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
author = models.CharField(max_length=100)

def __str__(self):
return self.title
  1. Create serializers for different versions:
python
# serializers.py
from rest_framework import serializers
from .models import Post

class PostSerializerV1(serializers.ModelSerializer):
class Meta:
model = Post
fields = ['id', 'title', 'content', 'author']

class PostSerializerV2(serializers.ModelSerializer):
days_since_created = serializers.SerializerMethodField()

class Meta:
model = Post
fields = ['id', 'title', 'content', 'author', 'created_at', 'updated_at', 'days_since_created']

def get_days_since_created(self, obj):
from django.utils import timezone
from datetime import datetime

if not obj.created_at:
return 0

now = timezone.now()
diff = now - obj.created_at
return diff.days
  1. Create a ViewSet that handles versioning:
python
# views.py
from rest_framework import viewsets
from .models import Post
from .serializers import PostSerializerV1, PostSerializerV2

class PostViewSet(viewsets.ModelViewSet):
queryset = Post.objects.all()

def get_serializer_class(self):
if self.request.version == 'v1':
return PostSerializerV1
return PostSerializerV2
  1. Set up the URL patterns:
python
# urls.py
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from .views import PostViewSet

router = DefaultRouter()
router.register('posts', PostViewSet)

urlpatterns = [
path('api/<str:version>/', include(router.urls)),
]

Now, when users access /api/v1/posts/, they'll get the simplified version with basic fields, while /api/v2/posts/ will return the enhanced version with additional fields.

Best Practices for API Versioning

  1. Don't Version Unless Necessary: Only introduce a new version when you're making breaking changes
  2. Be Consistent: Use the same versioning scheme across your entire API
  3. Documentation: Clearly document what's different between versions
  4. Version in the URL for Public APIs: URL-based versioning is the most client-friendly approach
  5. Sunset Policy: Define when older versions will be retired and communicate this to users
  6. Gradual Migration: Encourage users to migrate to newer versions gradually

Handling Deprecation

When deprecating an API version:

  1. Announce deprecation in your API documentation
  2. Return deprecation warnings in the response headers
  3. Set a specific date for when the version will become unavailable
  4. Provide migration guides to help users update their code

Example of adding deprecation warnings:

python
from rest_framework.response import Response

class UserViewSetV1(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializerV1

def list(self, request, *args, **kwargs):
response = super().list(request, *args, **kwargs)
response['Warning'] = '299 - "This version of the API will be deprecated on January 1, 2024. Please migrate to v2."'
return response

Summary

API versioning is a crucial aspect of maintaining a stable and evolving API. Django REST Framework provides several versioning schemes that can be easily implemented to ensure backward compatibility as your API evolves.

Key takeaways:

  • Choose a versioning scheme that works best for your API consumers
  • Configure versioning in your Django REST Framework settings
  • Create version-specific serializers and viewsets or handle versioning logic within viewsets
  • Follow best practices for deprecation and API evolution

By implementing proper versioning from the start, you create a path for your API to evolve while maintaining trust with your API consumers.

Additional Resources

  1. Django REST Framework Versioning Documentation
  2. REST API Versioning Best Practices
  3. API Versioning Methods, a Brief Reference

Exercises

  1. Implement URL path versioning for an existing API endpoint
  2. Create a new API endpoint with two different versions that return different data structures
  3. Implement a deprecation warning for a v1 API that encourages users to migrate to v2
  4. Build an API that can handle both query parameter versioning and header-based versioning
  5. Create a middleware that logs which API versions are being accessed to help you determine when it's safe to retire an old version


If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)