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:
- Backward Compatibility: Allows existing clients to continue working even as your API evolves
- Progressive Enhancement: Lets you add new features without breaking existing functionality
- Deprecation Strategy: Provides a clear path for phasing out older API versions
- 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:
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:
# 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:
# 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
# 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:
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:
- First, define your models:
# 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
- Create serializers for different versions:
# 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
- Create a ViewSet that handles versioning:
# 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
- Set up the URL patterns:
# 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
- Don't Version Unless Necessary: Only introduce a new version when you're making breaking changes
- Be Consistent: Use the same versioning scheme across your entire API
- Documentation: Clearly document what's different between versions
- Version in the URL for Public APIs: URL-based versioning is the most client-friendly approach
- Sunset Policy: Define when older versions will be retired and communicate this to users
- Gradual Migration: Encourage users to migrate to newer versions gradually
Handling Deprecation
When deprecating an API version:
- Announce deprecation in your API documentation
- Return deprecation warnings in the response headers
- Set a specific date for when the version will become unavailable
- Provide migration guides to help users update their code
Example of adding deprecation warnings:
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
- Django REST Framework Versioning Documentation
- REST API Versioning Best Practices
- API Versioning Methods, a Brief Reference
Exercises
- Implement URL path versioning for an existing API endpoint
- Create a new API endpoint with two different versions that return different data structures
- Implement a deprecation warning for a v1 API that encourages users to migrate to v2
- Build an API that can handle both query parameter versioning and header-based versioning
- 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! :)