All Articles

19 min read • By Kyle Truong • Published 8 Jun 2017

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

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, and it works through HTTP requests and responses and carefully structured URL routes to represent specific resource(s).

It looks a lot like this:

HTTP Model

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)
Content-Type: application/x-www-form-urlencoded
Content-Length: length
Accept-Language: en-us
Accept-Encoding: gzip, deflate
Connection: Keep-Alive


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

<h1>Hello, World!</h1>

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.

This is not 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:

  • 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 auth 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:

REST API endpoints

We structure our endpoints in accordance with common RESTful guidelines so that we have clear endpoints that return expected resources.


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
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
(auth-api) ktruong:auth-api ktruong$ touch
(auth-api) ktruong:auth-api ktruong$ startproject auth_api
(auth-api) ktruong:auth-api ktruong$ cd auth_api
(auth-api) ktruong:auth_api ktruong$ ls
(auth-api) ktruong:auth_api ktruong$ python startapp users
(auth-api) ktruong:auth_api ktruong$ python startapp todos
(auth-api) ktruong:auth_api ktruong$ ls
auth_api todos users
(auth-api) ktruong:auth_api ktruong$


AUTH_USER_MODEL = 'users.User'

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 I feel 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/

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'^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')),

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/

from rest_framework.decorators import api_view
from rest_framework.response import Response
from rest_framework.reverse import reverse

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),

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/

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'),
// users/

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'),

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(

    elif request.method == 'POST':
        serializer = SnippetSerializer(
        if serializer.is_valid():
            return Response(, 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.
        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(

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

    elif request.method == 'DELETE':
        return Response(status=status.HTTP_204_NO_CONTENT)

Examples from

So again, we see that the things that views essentially do are:

  • 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):

class TodoDetail(generics.RetrieveUpdateDestroyAPIView):
    serializer_class = TodoSerializer

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

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 parsing 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:

Try not to overthink it too much and remember that the 10 or so lines of code using class-based views does 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/

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)

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.

Let’s write our serializers for Todos and Users:

// Todos/

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/

from rest_framework import serializers

from users.models import User

class UserSerializer(serializers.HyperlinkedModelSerializer):
    todos = serializers.HyperlinkedRelatedField(
    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))
        return user

    def update(self, instance, validated_data):
        for field in validated_data:
            if field == 'password':
                instance.__setattr__(field, validated_data.get(field))
        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',

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 attribute on the model getting mapped to a field in its respective table.

We’ll create two models, Todo and User, ad we’ll create a many-to-one relationship (foreign key) from our Todoes to our Users, so a User can have many Todos but a Todo will only have one User.

// todos/

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',)
// users/

from django.db import models
from django.contrib.auth.models import AbstractUser

class User(AbstractUser):

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 makemigrations todos users
ktruong$ python migrate
ktruong$ python createsuperuser
ktruong$ python 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.

Django REST Framework admin example


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.