Django REST Framework & Better API Versioning (with Semantic Versioning)

Mike Helmick
2 min readApr 3, 2021

--

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:

Example of how DRF suggests checking on version in viewsets.

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.

Example of how DRF suggests checking on version in viewsets.

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:

  1. if self.request.version is ≥1.1.0 and <2.0.0, return TeeBallSupportedGameSerializer
  2. if self.request.version is >2.0.0, return RevampedGameSerializer
  3. 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.

Relative Links

--

--