In this tutorial we will build the API for a blog app featuring CRUD (Create-Read-Update-Delete) functionality with Django Rest Framework.

It’s helpful–but not required–to have previous experience with Django. If you’re looking for a beginner-friendly guide I’ve written an entire book, Django for Beginners, on the subject.

Complete source code can be found on Github.

SPAs

Modern web applications are increasingly built as Single-Page-Applications (SPAs) which feature a distinct frontend and backend. As opposed to the traditional monolith approach in Django itself–and other web framework like Ruby on Rails–SPAs require a backend API that can then be consumed by multiple frontends as needed. This approach works well when a company needs multiple frontend applications–mobile web, iOS, Android–to run off the same database. It is also arguably more “future-proof” since the frontend can change to whatever the flavor-of-the-month JavaScript framework is, but the backend remains stable the whole time.

The downside is that it takes more time and code to create a separate frontend/backend for projects.

RESTful APIs

An API (Application Programming Interface) provides an interface for developers to interact with an application’s database. Instead of just giving someone full access to a database, an API sets up rules, permissions, and endpoints for various functionality: login, logout, reading a list of blogs, individual blog details, and so on.

The traditional way to construct a web API is via REST (Representational State Transfer), a well-established architecture for how websites can communicate with one another. Since computers communicate via the web this means using the HTTP protocol which supports a number of common “methods” (also called “verbs”) such as GET, PUT, POST, and DELETE.

There are also a host of related access codes that indicate whether a request was successful (200), redirected (301), missing (404), or worse (500).

JSON

It’s important to note that since an API is communicating with another computer the information being shared is not what would be sent for a standard web page. When your browser requests, for example, the Google homepage, it sends HTTP requests and receives HTTP responses with HTML, CSS, JavaScript, images, and so on.

An API is different. Typically we’re only interested in the data from a database. This data is often transformed into JSON format to be efficiently transmitted about. The API will also have a series of well-defined rules for how a frontend client can interact with it via a REST architecture. To register a new user the frontend framework will need access an API endpoint called, for example, /api/register. This API endpoint contains both a specific URL route and its own set of permissions.

Setup

We will start the tutorial by building the models for a blog app in Django. Then we can add Django Rest Framework to transform it into a RESTful API.

First create a new directory on your computer for our code. I’ll place it on the Desktop in a folder called api but you can place it anywhere. Then configure our project.

$ cd ~/Desktop
$ mkdir api && cd api
$ pipenv install django
$ pipenv shell
(api) $ django-admin startproject myproject .
(api) $ python manage.py startapp blog

We’re using Pipenv to install Django and activate a local environment. The name will be something like api-XXX where the “XXX” is random. I’ve shortened it to api in the tutorial for brevity. Then we create a new Django project called myproject as well as our first app blog.

Since we’ve added a new app we need to tell Django about it. So make sure to add blog to our list of INSTALLED_APPS in the settings.py file.

# myproject/settings.py
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',

    'blog',
]

Our database model will be deliberately quite basic. Let’s create four fields: title, content, created_at, and updated_at.

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

class Blog(models.Model):
    title = models.CharField(max_length=50)
    content = models.TextField()
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    def __str__(self):
        return self.title

Note that we’re also defining what the __str__ representation of the model should be which is a Django best practice.

Now update our database by first creating a new migration file and then applying it.

(api) $ python manage.py makemigrations blog
(api) $ python manage.py migrate blog

Good! We want to view our data in Django’s excellent built-in admin app so let’s add Blog to it as follows.

# blog/admin.py
from django.contrib import admin
from . models import Blog

admin.site.register(Blog)

Then create a superuser account so we can login. Type the command below and enter all the prompts.

(api) $ python manage.py createsuperuser

Now we can start up the local web server.

(api) $ python manage.py runserver

Navigate to localhost:8000/admin and login with your superuser credentials.

Admin view

Click on “+ Add” button next to Blog and enter in some new content.

Admin add

I’ve created three new blog posts that you can see here.

Admin list

And we’re done with the Django part! Since we’re creating an API we will not need to create templates and views. Instead it’s time to add Django Rest Framework to take care of transforming our model data into an API.

Django Rest Framework

DRF takes care of the heavy lifting of transforming our database models into a RESTful API. There are two main steps to this process: first a serializer is used to transform the data into JSON so it can be sent over the internet, then a View is used to define what data is sent. For example in our blog data we currently have four fields. But let’s say we only want to expose three to our API. We can do that in the view.

Let’s see it in action. First stop the local server with Control+c and use Pipenv to install Django Rest Framework.

(api) $ pipenv install djangorestframework

Then add it to the INSTALLED_APPS section of our settings.py file.

# myproject/settings.py
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',

    'rest_framework',

    'blog',
]

I like to add a space between third-party apps like rest_framework and my own apps like blog.

Now create a new serializers.py file in our blog app.

(api) $ touch blog/serializers.py

Remember that the serializer is used to convert our data into JSON format. That’s it. Here’s what it looks like.

# blog/serializers.py
from rest_framework import serializers
from . import models


class BlogSerializer(serializers.ModelSerializer):

    class Meta:
        fields = ('id', 'title', 'content', 'created_at', 'updated_at',)
        model = models.Blog

On the top two files we’re importing serializers from DRF and our models. Next we create a serializer class and create a Meta class within it. The fields controls which database attributes are available. In this case we’re exposing all our fields including id which is the primary key Django automatically adds to all database records.

Next we need to create our views. Just as Django has generic class based views, so too DRF has generic views we can use. Let’s add a view to list all blog posts and a detail view for a specific post.

Update the views.py file in blog as follows.

# blog/views.py
from rest_framework import generics

from . import models
from . import serializers


class BlogList(generics.ListAPIView):
    queryset = models.Blog.objects.all()
    serializer_class = serializers.BlogSerializer


class BlogDetail(generics.RetrieveAPIView):
    queryset = models.Blog.objects.all()
    serializer_class = serializers.BlogSerializer

At the top we import generics from DRF as well as our models and serializers files. Then we create two views: BlogList and BlogDetail. Both are just for GETs however RetrieveAPIView is for a single instance of a model. The complete list of generic views is on the DRF site.

The final piece is urls. We need to create the url routes–known as endpoints in an API–where the data is available.

Start at the project-level urls.py file.

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


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

We’ve added include to the second line of imports and then created a path called api/ for our blog app.

Next create our blog app urls.py file.

(api) $ touch blog/urls.py

And then include the code below.

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

from . import views

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

All blog routes will be at api/ so our BlogList which has the empty string '' will be at api/ and BlogDetail at ‘api/#’ where “#” represents the primary key of the entry. For example, the first blog post has a primary id of 1 so it will be at the route api/1, the second post at api/2, and so on.

Browsable API

Time to view our work and check out a DRF killer feature. Start up the server.

(api) $ python manage.py runserver

Then go to http://127.0.0.1:8000/api/.

Blog list API view

Check out that! The api/ endpoint displays all three of my blog posts in JSON format. It also shows in the header that only GET, HEAD, OPTIONS are allowed. No POSTing of data.

Now go to http://127.0.0.1:8000/api/1/ and you’ll see only the data for the first entry.

Blog detail API view

Implementing CRUD

I promised at the beginning of the article that this tutorial would cover not just reading/getting content but the full CRUD syntax. Watch how easy DRF makes it to transform our API into one that supports CRUD!

Open up the blog/views.py file and change class BlogDetail(generics.RetrieveAPIView) to class BlogDetail(generics.RetrieveUpdateDestroyAPIView).

# blog/views.py
...
class BlogDetail(generics.RetrieveUpdateDestroyAPIView):
    queryset = models.Blog.objects.all()
    serializer_class = serializers.BlogSerializer

Now refresh the page at http://127.0.0.1:8000/api/1/ and you can see updates in our graphical UI.

Blog CRUD

You can use the “Delete” button to delete content, “Put” to either update or create new content, and “Get” to retrieve it as before. For example, navigate to a URL endpoint without content such as http://127.0.0.1:8000/api/4/.

Then use the graphical interface for PUT at the bottom of the page to create a new entry.

Blog create

Next Steps

We’ve only scratched the surface of what DRF can do. Another popular feature is ViewSets which combined with a Router can dramatically speed up writing complex APIs.

If you haven’t already done it, now is a good time to complete the official tutorial on DRF.

And if you’d like to read an in-depth guide to DRF–for example one that covers building out a complete, robust API with user authentication, permissions, throttling, etc–email at [email protected] and let me know! If enough people are interested I’d love to write a short ebook on the topic.