-
Book Overview & Buying
-
Table Of Contents
-
Feedback & Rating

Django in Production
By :

The most popular and widely used API is the REST API. Throughout this book, we shall be working with the REST API. REST has been around for more than two decades, and every company has its interpretation and implementation. In the following section, we shall try to put all the best practices used in the industry into practice.
Opinionated note
The RESTful API is not a protocol; instead, it is a standard convention that developers follow. There is no right or wrong while designing RESTful APIs. Since there is no enforced standard, the details we will provide are purely opinionated and come from my past experiences. You are free to pick the points you like and leave out the things that you feel are not relevant to your implementations.
Let’s look at a few generic good practices that developers use in the industry while defining RESTful endpoints:
# To get all blogs Avoid GET /get-all-blogs, rather use GET /blogs # To delete a particular blog Avoid POST /delete-blog rather use DELETE /blogs/<blogId> # To create a new blog with POST request Avoid POST /create-new-blog rather use POST /blogs # To update an existing blog with a PUT request Avoid PUT /update-blog rather use PUT /blogs/<blogId>
GET
: To retrieve an entity, be it a list or detailPOST
: To create any new entityPUT
: To Update an entityPATCH
: To partially update an entityDELETE
: To delete an entityid
after the endpoint to retrieve the information. For example, to get a list of blogs, use GET /blogs
, and to get the details of one blog, use GET /
blogs/<blog id>
.GET /
blogs/<blog id>/comments
./v1/blogs/
and /v2/blogs
. We will learn more about this later, in the Using API versioning section.200
is for any request responding with the data successfully, 201
is for creating a new entry, and so on.400
for bad requests and 404
for requested data not found.DRF is a framework that helps us create the REST endpoint faster. It’s the responsibility of the developer to write scalable and maintainable code while following the best practices. Let’s look at some best practices that we can implement using DRF.
Creating versions of an API is probably the most important thing to follow when working with clients whose updates are not under your control. An example of this is working on a mobile app. Once an end user installs a given mobile app, with a given API integrated, we have to support the given API until the end user updates the mobile app version with the newer API.
While creating an endpoint, a developer should consider all the future requirements possible, along with all the corner cases. However, just like it is not possible to predict the future, a developer cannot always foresee how the current API design might have to be redesigned. A redesign would mean breaking the contract between the client and the server. This is when the importance of API versioning comes into the picture. A well-versioned API will implement a new contract without breaking any of the existing clients.
There are multiple ways to implement API versioning:
Accept
header, whenever there is a new version, the client doesn’t need to update any endpoint whenever a new version is created:GET /bookings/ HTTP/1.1 Host: example.com Accept: application/json; version=1.0
GET /v1/bookings/ HTTP/1.1 Host: example.com Accept: application/json
GET /something/?version=1.0 HTTP/1.1 Host: example.com Accept: application/json
GET /bookings/ HTTP/1.1 Host: v1.example.com Accept: application/json
DRF supports all four methods of API versioning out of the box and also gives the option to create our custom API version logic if needed. We shall explore URLPathVersioning primarily since it is one of the easiest and most popular ways of implementing versioning using DRF.
In URL path versioning, the API version is passed in the URL path, which makes it easy to identify on both the client and server side. To integrate URLPathVersioning
in DRF, add the following in the Django config/settings.py
file:
REST_FRAMEWORK = { 'DEFAULT_VERSIONING_CLASS': 'rest_framework.versioning.URLPathVersioning' }
Now, we must add the version to the URL path. It is important to name the URL parameter <version>
since DRF is expecting it to be <version>
by default. Here, <version>
is the URL’s pattern, which means that any URL that matches this pattern shall be linked to the views.
Important note
To learn more about urlpatterns
, go to https://docs.djangoproject.com/en/stable/topics/http/urls/.
It is advisable to add <version>
at the beginning of the URL, so let’s do that in the main config/urls.py
file:
urlpatterns = [ path('admin/', admin.site.urls), path('<version>/demo-app-version/', include('demo_app.urls')) ]
Once we have configured the URL with the <version>
, we can try to create a new view and retrieve the version in our view. Add the following code to your demo_app/urls.py
file:
from django.urls import path from demo_app import views urlpatterns = [ path('hello-world/', views.hello_world), path('demo-version/', views.demo_version), ]
We shall retrieve the API version in the view and return the version in response:
@api_view(['GET']) def demo_version(request, *args, **kwargs): version = request.version return Response(data={ 'msg': f'You have hit {version} of demo-api' })
Now, when we open http://127.0.0.1:8000/v1/demo-app-version/demo-version/
, we should be able to see the following screen:
Figure 1.7: Output showing which version we have hit for the given API
If we change the URL to http://127.0.0.1:8000/v9/demo-app/demo-version/
, then we’ll see that it returns v9. v9 might not have been released yet, so this might create confusion for the end user hitting the endpoint. To solve this problem, we shall see how we can customize the version class of DRF so that we can add constraints that can help us design better applications.
Let’s see how we can extend the URLPathVersioning
class provided by DRF to address the problem we just raised. First, create a file called demo_app/custom_versions.py
. This file will have a custom version class for each view, along with a default class for all the views that don’t have multiple versions yet:
from rest_framework.versioning import URLPathVersioning class DefaultDemoAppVersion(URLPathVersioning): allowed_versions = ['v1'] version_param = 'version' class DemoViewVersion(DefaultDemoAppVersion): allowed_versions = ['v1', 'v2', 'v3'] class AnotherViewVersion(DefaultDemoAppVersion): allowed_versions = ['v1', 'v2']
Let’s see what the preceding code does:
DefaultDemoAppVersion
class can be used for all the views that are created in demo_app
. It has an allowed_versions
attribute that lists all the allowed versions that can be used in the URL path whenever we use this class-based view. version_param
is the URL path parameter name that we have used to define the version; it can be anything, depending on how you name the parameter, but in our case, we are using <version>
, which is used in the config/urls.py
file. This class will be used for all the views that are created in the demo app by default until a new version is added, after which we will create an individual class, as shown next.DemoViewVersion
class will contain the list of all the allowed_versions
attributes for DemoView
that are allowed in the URL path.AnotherViewVersion
class will contain all the versions that are allowed for a different class.Add the following code to the demo_app/views.py
file to integrate the custom version class (note that the custom versioning_class
can be only linked to a class-based view, so we are using APIView
here):
from rest_framework.response import Response from rest_framework.views import APIView from demo_app import custom_versions class DemoView(APIView): versioning_class = custom_versions.DemoViewVersion def get(self, request, *args, **kwargs): version = request.version return Response(data={'msg': f' You have hit {version}'}) class AnotherView(APIView): versioning_class = custom_versions.AnotherViewVersion def get(self, request, *args, **kwargs): version = request.version if version == 'v1': # perform v1 related tasks return Response(data={'msg': 'v1 logic'}) elif version == 'v2': # perform v2 related tasks return Response(data={'msg': 'v2 logic'})
Let’s explore the code and understand what is happening under the hood when we use the custom version class:
DemoView
class is a class-based APIView
where we are passing the allowed versions for the view by the versioning_class
attribute. This allows the request object to have a version attribute that is parsed from the URL path. Since we have specified the DemoViewVersion
class, this view will only allow the v1
, v2
, and v3
versions in the URL path. Any other version in the path will result in a 404
response.AnotherView
class is a class-based view where we are passing AnotherViewVersion
as the versioning_class
attribute. In this view, we are bifurcating the request by checking different versions and responding differently whenever we have a v1
or v2
request.Now, to link the view logic to the demo_app/urls.py
file, add the following code:
urlpatterns = [ path('hello-world/', views.hello_world), path('demo-version/', views.demo_version), path('custom-version/', views.DemoView.as_view()), path('another-custom-version/', views.AnotherView.as_view()) ]
If we go to http://127.0.0.1:8000/v4/demo-app-version/custom-version/
in our browser, we shall see the following error as shown in Figure 1.8, since we have only allowed three versions in our custom versioning_class
:
Figure 1.8: 404 error message stating “Invalid version in URL path”
This serves our purpose of only allowing certain versions of the API; any other API version shall result in an error response.
Important note
Custom versioning can only be attached to class-based views. If you don’t pass any custom versioning_class
, then Django will pick DEFAULT_VERSIONING_CLASS
from the default settings.
Frameworks such as Ruby on Rails provide the functionality to automatically map requests to a given pattern of URLs, depending on the functionality. DRF borrowed this concept and incorporated it into the framework as the Routers feature. Though this is a wonderful concept to learn and experiment with, developers should avoid it in production since this goes against the principle of Django: “Explicit is better than implicit.”
Django mentions that it should not show too much of the magic. I have personally seen legacy systems where developers added Router and after a couple of months, when a different developer wanted to fix a bug in the view, they were unable to find the corresponding view directly before having the “aha!” moment of identifying the Router concept.
Opinionated note
Avoiding the use of Router is something I have learned the hard way and have seen multiple developers avoid in production. But this is also an opinion that was developed through a bad experience; you can always try to implement it in a better way in your project.
If you want to learn more about Router, you can do so here: https://www.django-rest-framework.org/api-guide/routers/.
With that, we’ve learned how to create RESTful APIs and work with versioning. Now, let’s learn how to work with views using DRF. We mainly write business logic inside views.
Change the font size
Change margin width
Change background colour