Building an API with Django REST Framework and Class-Based Views

This article first appeared on Codeburst.io
What and Why Are We Building?
With the rise of Single-Page Applications and the trend of separating monoliths into services with distinct front and backends, knowing how to make your own RESTful API for your backend is more important than ever.
Officially, a RESTful API is a Representational State Transfer-ful Application Programming Interface. Big words, but what it really boils down to is putting data onto your web server in a way that’s accessible to other servers and clients, working through HTTP requests and responses and carefully structured URL routes to represent specific resources.
It looks a lot like this:

http://wiki.hashphp.org/HttpPrimer
HTTP is short for Hypertext Transfer Protocol and it’s a set of rules that dictate how data is packaged and communicated throughout the web. There are other protocols that go along with HTTP, but HTTP will be the focus for now for simplicity.
An HTTP request looks something like this:
POST /cgi-bin/process.cgi HTTP/1.1
User-Agent: Mozilla/4.0 (compatible; MSIE5.01; Windows NT)
Host: www.tutorialspoint.com
Content-Type: application/x-www-form-urlencoded
Content-Length: length
Accept-Language: en-us
Accept-Encoding: gzip, deflate
Connection: Keep-Alive

licenseID=string&content=string&/paramsXML=string

https://www.tutorialspoint.com/http/http_requests.htm
And an HTTP response looks something like this:
HTTP/1.1 200 OK
Date: Mon, 27 Jul 2009 12:28:53 GMT
Server: Apache/2.2.14 (Win32)
Last-Modified: Wed, 22 Jul 2009 19:15:56 GMT
Content-Length: 88
Content-Type: text/html
Connection: Closed


<body>
<h1>Hello, World!</h1>
</body>
</html>

https://www.tutorialspoint.com/http/http_responses.htm
Clients send HTTP requests to the server and the server sends back HTTP responses. Many things can fit within requests and responses, though most of the times the data is either metadata about the request/response or some kind of JSON string or both, when dealing with APIs.
This is a brief walkthrough of how to make a barebones API for a ‘Todo list’ application using the Django Rest Framework.
Keep in mind this isn’t a comprehensive guide, so you should be somewhat familiar with making AJAX calls with JavaScript and Django itself. You can make an API in any modern language and the concepts behind it are similar but I am choosing Python because:

I know it
It’s clean and explicit
Django and Django Rest Framework are both mature, stable, and well-documented
Django and Django Rest Framework gives you a lot out of the box
Pluggable authentication systems
Serializers
Views
Browseable client and admin panel
Auto-generated documentation for your API
An awesome ORM
Highly customizable on every level

In a nutshell, this is what we’ll be creating:

We structure our endpoints in accordance with common RESTful guidelines so that we have clear endpoints that return expected resources.
Setup
First, we use virtualenv and virtualenvwrapper to make a virtual environment to install Python packages in a way that doesn’t interfere with other projects and environments. In this environment, we install Django, Django Rest Framework, and coreapi (for Django Rest Framework).
ktruong:auth-api ktruong$ which python3
/usr/local/bin/python3
ktruong:auth-api ktruong$ mkvirtualenv –python=/usr/local/bin/python3 auth-api

(auth-api) ktruong:auth-api ktruong$

pip install django djangorestframework coreapi

Now, we create the django project, make folders, and adjust some project settings:
(auth-api) ktruong:auth-api ktruong$ pwd
/Users/ktruong/git_projects/blogs/auth-api
(auth-api) ktruong:auth-api ktruong$ touch README.md
(auth-api) ktruong:auth-api ktruong$ django-admin.py startproject auth_api
(auth-api) ktruong:auth-api ktruong$ cd auth_api
(auth-api) ktruong:auth_api ktruong$ ls
auth_api manage.py
(auth-api) ktruong:auth_api ktruong$ python manage.py startapp users
(auth-api) ktruong:auth_api ktruong$ python manage.py startapp todos
(auth-api) ktruong:auth_api ktruong$ ls
auth_api manage.py todos users
(auth-api) ktruong:auth_api ktruong$

settings.py
INSTALLED_APPS = [
‘django.contrib.admin’,
‘django.contrib.auth’,
‘django.contrib.contenttypes’,
‘django.contrib.sessions’,
‘django.contrib.messages’,
‘django.contrib.staticfiles’,
‘rest_framework’,
‘todos’,
‘users’,
]

AUTH_USER_MODEL = ‘users.User’

https://gist.github.com/KTruong008/e951617ed1bc55611e92c5531f7a9005
Notice how we explicitly set our AUTHUSERMODEL to a custom User model (we’ll write the actual model, users.User, later). We could use the default User model that comes from Django but it becomes unnecessarily complicated to change it down the road.
A solution is to write our own User model that subclasses the same AbstractUser model that Django’s User model subclasses. Doing this will give us the same functionality but allow us to easily customize our User model down the road.
Project Level URL Routing
We’ll be building the features in this order:
URLs → views → serializers → models
This means we’ll be referencing some files before we make them, which may feel strange, but doing it this way is more intuitive because it follows the path of the HTTP request more closely.
When the request first comes into our server, we need to decide where to route that request. Kind of like a receptionist to our web server, our top-most URL routes will route the request to the proper modules and views.
We’ll use the innermost auth_api folder to store the top-most url routes along with our project-wide settings:
auth_api/urls.py
from django.conf.urls import url, include
from django.contrib import admin
from rest_framework.documentation import include_docs_urls

from auth_api import views

urlpatterns = [
url(r’^admin/’, admin.site.urls),
url(r’^docs/’, include_docs_urls(title=’Todo API’, description=’RESTful API for Todo’)),

url(r’^$’, views.api_root),
url(r’^’, include(‘users.urls’, namespace=’users’)),
url(r’^’, include(‘todos.urls’, namespace=’todos’)),
]

https://gist.github.com/KTruong008/deaeee721a5b0e17870db607e5974e8c
When Django parses the incoming request, it will use regex to match the URL to the urlpatterns we write and forward the request to the place we want, which could contain more URL routes or a view.
We set up routes for our Django admin, documentation (automatically generated thanks to DRF), routes for users, routes for todos, and our API root.
Making the Root View of the API
One of the routes we defined earlier routed to views.api_root. Though not necessary, making a root view that acts as a table of contents to other routes in your API when requests matches your domain name exactly is easy to implement and improves developer experience.
The urlpatterns we wrote route requests and the views we will write will handle those requests and return HTTP responses. To write our apiroot, we can use DRF’s built in decorator, @apiview, to wrap our view with some nice utilities:

Specifies which HTTP methods we allow the view to respond to
Wraps normal HTTP request and response objects to provide a more uniform interface to work with HTTP data.

auth_api/views.py
from rest_framework.decorators import api_view
from rest_framework.response import Response
from rest_framework.reverse import reverse

@api_view([‘GET’])
def api_root(request, format=None):
return Response({
‘users’: reverse(‘users:user-list’, request=request, format=format),
‘todos’: reverse(‘todos:todo-list’, request=request, format=format),
})

https://gist.github.com/KTruong008/a6f389cacf118b7258ed519879491761
Configuring URLs for Users and Todos
We need to create routes for when the request matches /todos/* or /users/* (as per the table we made earlier) and route them to the proper views to be handled:
todos/urls.py
from django.conf.urls import url
from rest_framework.urlpatterns import format_suffix_patterns
from todos import views

urlpatterns = [
url(r’^todos/$’, views.TodoList.as_view(), name=’todo-list’),
url(r’^todos/(?P<pk>[0-9]+)/$’, views.TodoDetail.as_view(), name=’todo-detail’),
]

https://gist.github.com/KTruong008/22bfed3f00ffb8adf2d130a7f9600bd4
users/urls.py
from django.conf.urls import url
from rest_framework.urlpatterns import format_suffix_patterns
from users import views

urlpatterns = [
url(r’^users/$’, views.UserList.as_view(), name=’user-list’),
url(r’^users/(?P<pk>[0-9]+)/$’, views.UserDetail.as_view(), name=’user-detail’),
]

https://gist.github.com/KTruong008/ddc26d308719cad8afc6fbca56f2c9f3
Writing the Views
Views handle the request and return a response and by handling, I mean we can do anything we want with it – from hitting the database, modifying the request, structuring the response, or injecting our own logic into it.
With respect to APIs, all the core views often do the same thing conceptually:

Parse the request for data and the HTTP method
Query the database (DB) to fetch the model object(s), if needed
Serialize the data (we’ll discuss this more later)
Do something with the object/data (create, read, update, delete)
Return an HTTP response

Views are most often written as functions and an example of a function-based view that handles our API request looks something like this:
@api_view([‘GET’, ‘POST’])
def snippet_list(request):
“""
List all snippets, or create a new snippet.
"""
if request.method == ‘GET’:
snippets = Snippet.objects.all()
serializer = SnippetSerializer(snippets, many=True)
return Response(serializer.data)

elif request.method == ‘POST’:
serializer = SnippetSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

@api_view([‘GET’, ‘PUT’, ‘DELETE’])
def snippet_detail(request, pk):
"""
Retrieve, update or delete a snippet instance.
"""
try:
snippet = Snippet.objects.get(pk=pk)
except Snippet.DoesNotExist:
return Response(status=status.HTTP_404_NOT_FOUND)

if request.method == ‘GET’:
serializer = SnippetSerializer(snippet)
return Response(serializer.data)

elif request.method == ‘PUT’:
serializer = SnippetSerializer(snippet, data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

elif request.method == ‘DELETE’:
snippet.delete()
return Response(status=status.HTTP_204_NO_CONTENT)

Examples from http://www.django-rest-framework.org/tutorial/2-requests-and-responses/
https://gist.github.com/KTruong008/254888fc3553ef9929c26997522ccbca
Again, we see that ‘views’ essentially do is:

Parse the request for data and the HTTP method
Query the database (DB) to fetch the model object(s), if needed
Serialize the data (we’ll discuss this more later)
Do something with the object/data (create, read, update, delete)
Return an HTTP response

It’s relatively straight-forward and explicit, but imagine having to write our views likes this for:

Todo_detail
Todo_list
User_detail
User_list

There would be a lot of logic duplication. Instead, we could use an object-oriented approach and use classes and inheritance to reuse common blocks of logic. Using class-based views, we could write our views like this:
from todos.models import Todo
from rest_framework import generics
from rest_framework.response import Response
from rest_framework.reverse import reverse

from todos.serializers import TodoSerializer

class TodoList(generics.ListCreateAPIView):
queryset = Todo.objects.all()
serializer_class = TodoSerializer

def perform_create(self, serializer):
serializer.save(user=self.request.user)

class TodoDetail(generics.RetrieveUpdateDestroyAPIView):
serializer_class = TodoSerializer

def get_queryset(self):
return Todo.objects.all().filter(user=self.request.user)

https://gist.github.com/KTruong008/696d32650f1c69b1a11c3f8e81362ea0
With that we accomplish the exact same functionality as the example above that uses function-based views, only with significantly less code.
But less code is not always better, so you’ll have to decide where to draw the line in terms of how explicit or terse you want your code to be. Personally, I feel class-based views strike that perfect balance, plus they force you to grow as a developer by exposing you to classes, inheritance, and object-oriented programming, which is probably the bulk of the code you’ll be reading and/or writing as a developer.
In the above classes, we subclass generic classes provided by DRF and the generic classes provide methods and properties that encapsulate the common logic so we don’t have to keep rewriting it, but we can still access, overwrite, and customize the logic if needed.
Blocks of logic like the ones that parse the DB for the correct model instance–or carry out error handling, or determining which serializer to use and how to instantiate it, or structure the response, or parse the request–are already written for you and are readily available to the classes that you write if you subclass the generic ones.
There is honestly a lot of cleverly written code hidden behind these generic views with a lot of concepts and techniques to learn from that are beyond the scope of this guide, but the best way to really learn and understand is to download the source code, read it, and tinker. I also recommend reading the documentation on these topics:
https://docs.djangoproject.com/en/1.11/topics/class-based-views/
http://www.django-rest-framework.org/api-guide/generic-views/
https://github.com/encode/django-rest-framework/tree/master
https://github.com/django/django
Try not to overthink it too much and remember that the 10 or so lines of code using class-based views do the exact same thing as the many more lines of codes using function-based views in the above example, just more concise.
Repeat for Users:
users/views.py
from users.models import User
from rest_framework import generics
from rest_framework.response import Response
from rest_framework.reverse import reverse

from users.serializers import UserSerializer

class UserList(generics.ListCreateAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer

class UserDetail(generics.RetrieveUpdateDestroyAPIView):
serializer_class = UserSerializer

def get_queryset(self):
return User.objects.all().filter(username=self.request.user)

https://gist.github.com/KTruong008/242b82436e90e9fb626c495d7df5ed64
Writing the Serializers
DRF serializers provide the service of serialization and deserialization. Serialization is the process of translating data structures into a format that can be stored, which in this case means turning querysets and model instances into native Python datatypes and then into JSON. Deserialization is the opposite, taking JSON and turning it into native Python datatypes and then into model instances.
Serializers are not magic.
If you’ve made an API in any language then you’ve used the same concepts serializers use. Serializers just give you a convenient interface to take data in one form and convert it into another. DRF serializers use an interface similar to that of Django forms in that you define the fields of the model you wish to serialize/deserialize and, when you instantiate the serializer with data, it will do the field validation for you.
http://www.django-rest-framework.org/api-guide/serializers/
Let’s write our serializers for Todos and Users:
Todos/serializers.py
from rest_framework import serializers

from todos.models import Todo

class TodoSerializer(serializers.HyperlinkedModelSerializer):
user = serializers.ReadOnlyField(source=’user.username’)

class Meta:
model = Todo
fields = (‘url’, ‘id’, ‘created’, ‘name’, ‘user’)
extra_kwargs = {
‘url’: {
‘view_name’: ‘todos:todo-detail’,
}
}

users/serializers.py
from rest_framework import serializers

from users.models import User

class UserSerializer(serializers.HyperlinkedModelSerializer):
todos = serializers.HyperlinkedRelatedField(
many=True,
view_name=’todos:todo-detail’,
read_only=True
)
password = serializers.CharField(write_only=True)

def create(self, validated_data):
user = User(
username=validated_data.get(‘username’, None)
)
user.set_password(validated_data.get(‘password’, None))
user.save()
return user

def update(self, instance, validated_data):
for field in validated_data:
if field == ‘password’:
instance.set_password(validated_data.get(field))
else:
instance.__setattr__(field, validated_data.get(field))
instance.save()
return instance

class Meta:
model = User
fields = (‘url’, ‘id’, ‘username’,
‘password’, ‘first_name’, ‘last_name’,
’email’, ‘todos’
)
extra_kwargs = {
‘url’: {
‘view_name’: ‘users:user-detail’,
}
}

https://gist.github.com/KTruong008/79b5d56df4e78663410caa14565cce41
Notice that UserSerializer is much more complex than TodoSerializer. Serializers, like our class-based views, come in many varieties and can inherit from many generic base classes. Within those base classes are methods and properties we can overwrite to customize what happens at certain hooks, like when a serializer updates or saves data.
We customize UserSerializer because we want to use the hashing feature that comes with Django’s AbstractUser when updating and saving users for security reasons.
Writing the Models
From client to url routes to views to serializers, and now to the last part in our little app, the models. Our models are what define our data. Django models get written in normal Python classes and they get mapped to SQL DBs, each attributed on the model getting mapped to a field in its respective table.
https://docs.djangoproject.com/en/1.11/topics/db/models/
We’ll create two models, Todo and User, and we’ll create a many-to-one relationship (foreign key) from our Todos to our Users, so a User can have many Todos but a Todo will only have one User.
todos/models.py
from django.db import models
from users.models import User

class Todo(models.Model):
created = models.DateTimeField(auto_now_add=True)
name = models.CharField(max_length=100, unique=True, blank=False, null=False)
user = models.ForeignKey(‘users.User’, related_name=’todos’, on_delete=models.CASCADE, null=False)

class Meta:
ordering = (‘created’,)

https://gist.github.com/KTruong008/1cb557b2c3dccf8c1367b57eef33a110
users/models.py
from django.db import models
from django.contrib.auth.models import AbstractUser

class User(AbstractUser):
Pass

https://gist.github.com/KTruong008/235e5d3bf198997311dd8aa173d0884a
We touched a bit on this earlier, but we don’t do anything extra with our User model. We use the same AbstractUser that the default Django User model uses, but defining our own User model gives us the ability to easily edit it later on while still retaining the same features as the default Django User.
Bringing It All Together
We’ve got all the code we need to make this API work, and now we just need to migrate our models and create a superuser.
ktruong$ python manage.py makemigrations todos users
ktruong$ python manage.py migrate
ktruong$ python manage.py createsuperuser
ktruong$ python manage.py runserver

Now go browse localhost:8000, login to the admin if needed, and play around with your new API.
The browsable API is just another out-of-the-box feature from DRF and it allows you to browse your API interactively in a browser. You can click through links, see relationships, see models and objects, perform any CRUD (create, read, update, delete) request, and do pretty much anything you can do with an API, but in a visible and interactive way.

Conclusion
We covered some fundamental concepts of building a RESTful API by creating a basic Todo API, with complete CRUD endpoints. Though very barebones, the instructions in this guide should serve as another step in the ladder to help you become a better developer and understand how to make your own API.
There’s still a lot more one can do to improve your API, such as adding authentication and authorization with JSON Web Tokens, adding unit tests, and more, which are also topics I plan on covering in the future.
Check out my other articles at:
https://medium.com/@ktruong008/latest

Link: http://blog.rangle.io/building-an-api-with-django-rest-framework-and-class-based-views/