Django REST Framework & Better API Versioning (with Semantic Versioning)
Django REST Framework (DRF) is a great tool for building your API.
As your product grows, so does your API; and sometimes you need to version the API. DRF offers a few versioning classes out-of-the-box (https://www.django-rest-framework.org/api-guide/versioning/).
In their example, they check self.request.version == "v1"
.
Now, if you need to make API changes for another app and you need to version it again (let’s say v2
), but not make changes to AccountSerializerVersion1
, you’re going to have to add a check so it doesn’t return the original AccountSerializer
if a client says v2
as the API version.
I opt for semantic versioning for things like GitHub releases, pypi package versions, and other software choices.
MAJOR version when you make incompatible API changes,
MINOR version when you add functionality in a backwards compatible manner, and
PATCH version when you make backwards compatible bug fixes.
Having to add a new feature to a product I work with, I thought, why not try it in DRF?
For this, you’ll need to a new dependency: semantic-version
Specific versions of libraries used in this post:
djangorestframework==3.12.4
semantic-version==2.8.5
Django==3.1.7
The following example is going to be adding a new Game type to a baseball product.
Previously, the client (mobile app) would only support Baseball and Softball
So, now that we want to add Tee-ball, we’d like older clients not to render Tee-ball games. Since Tee-ball is going to be a larger feature as a whole, we’ll introduce it in API version “1.1.0”
We’ve added a new GameType
and TEEBALL_INTRODUCED_VERSION
to the Game
model.
Given the DRF example, you’d need to check on a specific version:
The above code says “if the version isn’t 1.1.0, exclude Tee-ball games”, BUT if a client sends any newer API version that aren’t “1.1.0”, Tee-ball games are going to be excluded, which isn’t desired.
In our view, we’re going to use semantic-version
's SimpleSpec
and Version
classes to define which API versions should exclude Tee-ball games.
Now if the client sends “1.2.0”, they’ll still get back Tee-ball games.
Let’s do one more example with Serializers and a rework of an API.
1.0.0: Initial Game serializer.
1.1.0: Add Tee-ball related field.
2.0.0: Rework Game serializer because product requests have changed.
Again, in this example, if a client ever sent “1.2.0” they’d get back GameSerializer
and not TeeBallSupportedGameSerializer
. Eventually, when API 2.0.0 comes out, if they send anything other than “2.0.0” they’d get back GameSerializer
. To solve this, we can use semantic-version
's combined specifications:
We’ve added a new version constant to the Game
model REVAMPED_PRODUCT_INTRODUCED_VERSION
.
Our view is now checking:
- if
self.request.version
is ≥1.1.0 and <2.0.0, returnTeeBallSupportedGameSerializer
- if
self.request.version
is >2.0.0, returnRevampedGameSerializer
- Otherwise (meaning something <1.1.0 is sent), return the original
GameSerializer
Takeaways
Using semantic versioning along with the semantic-version
library can help bring dynamic, more robust version checking to your Django API.