Django Rest Framework is a powerful library that sits on top of existing Django projects to add robust web APIs. If you have an existing Django project with only models and a database–no views, urls, or templates required–you can quickly transform it into a RESTful API with a minimal amount of code.

In this tutorial we’ll create a basic Django To Do app and then convert it into a web API using serializers, viewsets, and routers.

You should have a basic understanding of how Django works and a local development configuration with Python 3 and pipenv. I’ve written a free book called Django for Beginners that introduces Django and has a dedicated chapter on local dev setup.

Complete source code on Github.

Initial Setup

Let’s start by creating a directory for our code, installing Django, and creating a new project. Execute the following on the command line.

$ cd Destkop
$ mkdir drf-tutorial && cd drf-tutorial
$ pipenv install django
$ pipenv shell
(drf-tutorial) $ django-admin startproject demo_project .
(drf-tutorial) $ python manage.py startapp todos

We’re placing the code on the Desktop but it can live anywhere you choose on your computer. We’ve used pipenv to install Django and then enter a dedicated virtual environment. And finally created a new Django project called demo_project and a todos app.

Since we have a new app we need to update our INSTALLED_APPS setting. Open demo_project/settings.py with your text editor and add todos at the bottom of the list.

# demo_project/settings.py
INSTALLED_APPS = [
  ...
  'todos',
]

Perform our first migration to set up the initial database at this point.

(drf-tutorial) $ python manage.py migrate

Next we can create a basic model for our Todo app which will just have a title, description, and add a __str__ method.

# todos/models.py
from django.db import models


class Todo(models.Model):
    title = models.CharField(max_length=200)
    description = models.TextField()

    def __str__(self):
        """A string representation of the model."""
        return self.title

Then create a dedicated migration file and migrate our change to the project database.

(drf-tutorial) $ python manage.py makemigrations todos
(drf-tutorial) $ python manage.py migrate todos

The final step is to update the admin.py file so the todos app is displayed in the Django admin.

# todos/admin.py
from django.contrib import admin

from .models import Todo

admin.site.register(Todo)

In a normal Django app at this point we would need to add urls, views, and templates to display our content. But since our end goal is an API that will transmit the data over the internet, we don’t actually need anything beyond our models.py file.

Let’s create a superuser account so we can log into the admin and add some data to our model.

(drf-tutorial) $ python manage.py createsuperuser
(drf-tutorial) $ python manage.py runserver

Navigate to http://127.0.0.1:8000/admin and login with your new superuser account.

Admin view

On the Admin homepage you should see the todos app. Click on “+ Add” button for Todos and add two new entries we can use.

Admin todos

Now it’s time for Django Rest Framework.

Django Rest Framework

The first step is to install Django Rest Framework and then create a new api app. All of our API information will be routed through here. Even if we had multiple apps in our project, we’d still only need a single api app to control what the API does.

On the command line stop the server with Control+c and then enter the following.

(drf-tutorial) $ pipenv install djangorestframework
(drf-tutorial) $ python manage.py startapp api

Next add both rest_framework and the api app to our INSTALLED_APPS setting. We also add default permissions. In the real-world we would set various permissions here so that only logged-in users could access the API, but for now we’ll just open up the API to everyone to keep things simple. The API is just running locally after all so there’s no real security risk.

# demo_project/settings.py
INSTALLED_APPS = [
    ...
    'rest_framework',
    'api',
    'todos',
]

REST_FRAMEWORK = {
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.AllowAny',
    ]
}

A traditional Django app needs a dedicated url, view, and template to translate information from that database onto a webpage. In DRF we instead need a url, view, and a serializer. The url controls access to the API endpoints, views control the logic of the data being sent, and the serializer performs the magic of converting our information into a format suitable for transmission over the internet, JSON.

If you’re new to APIs then serializers are probably the most confusing part of the equation. A normal webpage requires HTML, CSS, and JavaScript (usually). But our API is only sending data in the JSON format. No HTML. No CSS. Just data. The serializer translates our Django models into JSON and then the client app translates JSON into a full-blown webpage. The reverse, deserialization, also occurs when our API accepts a user input–for example submitting a new todo–which is translated from HTML into JSON then converted into our Django model.

So to repeat one last time: urls control access, views control logic, and serializers transform data into something we can send over the internet.

Within our api app we need to create a serializers.py file.

(drf-tutorial) $ touch api/serializers.py

Much of the magic comes from the serializers class within DRF which we’ll import at the top. We need to import our desired model and specify which fields we want exposed (usually you don’t want to expose everything in your model to the public).

# api/serializers.py
from rest_framework import serializers
from todos import models


class TodoSerializer(serializers.ModelSerializer):
    class Meta:
        fields = (
            'id',
            'title',
            'description',
        )
        model = models.Todo

Next up is our view. DRF views are very similar to traditional Django views and even come with several generic views that provide lots of functionality with a minimal amount of code on our part. We want a ListView as well as a DetailView of individual todo items. Here’s what the code looks like.

# api/views.py
from rest_framework import generics

from todos import models
from . import serializers

class ListTodo(generics.ListCreateAPIView):
    queryset = models.Todo.objects.all()
    serializer_class = serializers.TodoSerializer


class DetailTodo(generics.RetrieveUpdateDestroyAPIView):
    queryset = models.Todo.objects.all()
    serializer_class = serializers.TodoSerializer

Again DRF performs all the heavy lifting for us within its generics class that we import at the top. This is quite similar to generic class-based views in traditional Django. We specify our model and serializer for each of the views.

All that’s left is to update our URLs. At the project-level we want to include the api app. So we’ll add include to the second line imports and then add a dedicated URL path for it. Note that the format is api/v1/. It’s a best practice to always version your APIs since they are likely to change in the future but existing users might not be able to update as quickly. Therefore a major change might be at api/v2/ to support both versions for a period of time.

# demo_project/urls.py
from django.contrib import admin
from django.urls import include, path


urlpatterns = [
    path('admin/', admin.site.urls),
    path('api/v1/', include('api.urls')),
]

Finally we need to update the urls.py file in our our api app to display our views. The list of all todos will be at api/v1/. Individual todo items will be at their pk which is automatically set by Django for us. So the first todo will be at api/v1/1/, the second at api/v1/2/, and so on.

# api/urls.py
from django.urls import path

from . import views

urlpatterns = [
    path('', views.ListTodo.as_view()),
    path('<int:pk>/', views.DetailTodo.as_view()),
]

Ok, we’re done! That’s it. We now have an API of our To do project. Go ahead and start the local server with runserver.

(drf-tutorial) $ python manage.py runserver

Testing with the web browser

Fortunately DRF comes with a very nice graphical interface for our API, similar in some ways to Django’s admin app. If you simply go to an API endpoint you can see it visualized.

The list view of all items is at http://127.0.0.1:8000/api/v1/.

API ListView

And the DetailView is at http://127.0.0.1:8000/api/v1/1/.

API DetailView

You can even use the forms on the bottom of each page to create, retrieve, destroy, and update new todo items. When your APIs become even more complex many developers like to use Postman to explore and test an API. But the usage of Postman is beyond the scope of this tutorial.

Viewsets

As you build more and more APIs you’ll start to see the same patterns over and over again. Most API endpoints are some combination of common CRUD (Create-Read-Update-Delete) functionality. Instead of writing these views one-by-one in our views.py file as well as providing individual routes for each in our urls.py file we can instead use a ViewSet which abstracts away much of this work.

For example, we can replace our four views and four url routes with one viewset and one URL route. That sounds better, right? Here’s what the new code for views.py looks like with a viewset.

# api/views.py
from rest_framework import viewsets

from todos import models
from . import serializers


class TodoViewSet(viewsets.ModelViewSet):
    queryset = models.Todo.objects.all()
    serializer_class = serializers.TodoSerializer

The viewsets class does all the magic here, specifically the method ModelViewSet which automatically provides list as well as create, retrieve, update, and destroy actions for us.

We can update our urls.py file to be much simpler too as follows.

# api/urls.py
from django.urls import path

from . import TodoViewSet
from rest_framework.routers import DefaultRouter

router = DefaultRouter()
router.register('', TodoViewSet, base_name='todos')
urlpatterns = router.urls

Now if you look again at our pages you’ll see that the list view at http://127.0.0.1:8000/api/v1/ is exactly the same as before.

API ListView

And our detailview at http://127.0.0.1:8000/api/v1/1/has the same functionality–the same HTTP verbs allowed–though now it is called a “Todo Instance”.

Todo Instance

This saving of code may seem small and not worth the hassle in this simple example, but as an API grows in says with many, many API endpoints it often is the case that using viewsets and routers saves a lot of development time and makes it easier to reason about the underlying code.

Next steps

I am writing a book called REST APIs with Django. To be notified when it is available as well as to receive special discounts, please sign up for my newsletter below.