Developing Restful APIs with Python, Django and Django Rest Framework
Tags: python, django, backend, dev
Published: 2019-06-16 11:46:19

# Introduction

This article is a definitive guide for starters who want to develop projects with RESTful APIs using Python, Django and Django Rest Framework.

* **Django** is a web framework written in **Python**

* **Python** is an interpreted high-level programming language for general-purpose programming

* **API** or Application Programming Interface is a set of rules and mechanisms by which one application or component interacts with the others

* **REST** or Representational State Transfer is a software architecture

## REST APIs

As described in a dissertion by Roy Fielding,

> REST is an "architectural style' that basically exploits the existing technology and protocols of the web.

In simple definition, it is the data representation for a client in the format that is suitable for it. 

Hence, RESTful + API is a commonly used terminology for the implementation of such architecture and constraints (eg. in web services).

Here is an example **GET** request from GitHub's API

```s
$ curl https://api.github.com/users/joshuadeguzman
```

You will see an output similar to this

```json
{
  "login": "joshuadeguzman",
  "id": 20706361,
  "node_id": "MDQ6VXNlcjIwNzA2MzYx",
  "avatar_url": "https://avatars1.githubusercontent.com/u/20706361?v=4",
  "gravatar_id": "",
  "url": "https://api.github.com/users/joshuadeguzman",
  "html_url": "https://github.com/joshuadeguzman",
  "followers_url": "https://api.github.com/users/joshuadeguzman/followers",
  "following_url": "https://api.github.com/users/joshuadeguzman/following{/other_user}",
  "gists_url": "https://api.github.com/users/joshuadeguzman/gists{/gist_id}",
  "starred_url": "https://api.github.com/users/joshuadeguzman/starred{/owner}{/repo}",
  "subscriptions_url": "https://api.github.com/users/joshuadeguzman/subscriptions",
  "organizations_url": "https://api.github.com/users/joshuadeguzman/orgs",
  "repos_url": "https://api.github.com/users/joshuadeguzman/repos",
  "events_url": "https://api.github.com/users/joshuadeguzman/events{/privacy}",
  "received_events_url": "https://api.github.com/users/joshuadeguzman/received_events",
  "type": "User",
  "site_admin": false,
  "name": "Joshua de Guzman",
  "company": "@freelancer",
  "blog": "https://joshuadeguzman.me",
  "location": "Manila, PH",
  "email": null,
  "hireable": true,
  "bio": "Android Engineer at @freelancer. Building tools for humans.",
  "public_repos": 75,
  "public_gists": 2,
  "followers": 38,
  "following": 10,
  "created_at": "2016-07-28T15:19:54Z",
  "updated_at": "2019-06-16T10:26:39Z"
}
```

Shown above is a data set in **JSON** format.

> **JSON** or **JavaScript Object Notation** is an open-standard file format that uses human-readable text to transmit data objects consisting of attribute–value pairs and array data types.

Other formats include XML, INI, CSV, etc. But today, JSON is widely use for its structure is intuitive, making it comfortable to read and map domain objects no matter what programming language is being used.

## Python and Django


**Python**, according to its creator, Guido van Rossum, is a
> high-level programming language, and its core design philosophy is all about code readability and a syntax which allows programmers to express concepts in a few lines of code.

Python uses english like words representation (eg. for methods, reserve keywords and control flow) that makes it easier for any beginner to jump right into it. It also features dynamic type system meaning it verifies the type safety of program at runtime. It also does automatic memory management.

```python
print(5 + 5) # This will result to 10
```

**Django** is a high-level Python Web Framework that enables developers to deliver projects on time with clean and pragmatic design. 

Its flagship features include a design for fast development, a secure and scalable product.

## Quick Django Overview

Django's way of propagating changes to your database schema is by means of its **migration** modules.

Sample `User` model

```python
from django.db import models

class User(models.Model):
    first_name = models.CharField(max_length=50)
    middle_name = models.CharField(max_length=50)
    last_name = models.CharField(max_length=50)

    def __str__(self):
        return self.name
```

If any changes are made on your models, run `makemigrations`

```s
$ python manage.py makemigrations
```

Finally, you can synchronize the database with the set of models and migrations

```s
$ python manage.py migrate
```

# REST APIs with Django Rest Framework

**DRF** or Django REST Framework is a powerful and flexible toolkit for building Web APIs. It helps the developers to not reinvent the wheel by rolling out complex and solid REST API from scratch by themselves. Because when your projects become more and more complex, you will soon realise the need of using DRF or other helpful rest framework.

### 1. Installation & Project Setup

Create **project** directory

```s
$ mkdir djangoapi
```

Install **virtualenv** via [pip](https://pip.pypa.io/en/stable/installing/)

A **virtual environment** enables a project to have additional libraries or changes in packages within its environment without disturbing global or libraries of other environments.

> `pip` is a package management system used to install and manage software packages written in Python.

```s
$ pip install virtualenv
```

To create an environment folder in your project's directory

```s
$ cd djangoapi
$ virtualenv venv
```

To activate the environment
```s
$ source venv/bin/activate
```

To undo these changes to your path, simply run `deactivate`. More on [virtualenv](https://virtualenv.pypa.io/en/stable/userguide/).

Install **django**, **djangorestframework**

```s
$ pip install django
$ pip install djangorestframework
```

Creating a **django project**

```s
$ django-admin startproject blog
```

Running your **project**

```s
$ python manage.py runserver

System check identified no issues (0 silenced).

You have 15 unapplied migration(s). Your project may not work properly until you apply the migrations for app(s): admin, auth, contenttypes, sessions.
Run 'python manage.py migrate' to apply them.

August 16, 2018 - 09:58:36
Django version 2.1, using settings 'blog.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
```

The *unapplied migrations* refer to the default migration files included when you start a django project.

To synchronize these migration files, simply run `migrate`

```s
$ python manage.py migrate

Running migrations:
  Applying contenttypes.0001_initial... OK
  Applying auth.0001_initial... OK
  Applying admin.0001_initial... OK
  Applying admin.0002_logentry_remove_auto_add... OK
  Applying admin.0003_logentry_add_action_flag_choices... OK
  Applying contenttypes.0002_remove_content_type_name... OK
  Applying auth.0002_alter_permission_name_max_length... OK
  Applying auth.0003_alter_user_email_max_length... OK
  Applying auth.0004_alter_user_username_opts... OK
  Applying auth.0005_alter_user_last_login_null... OK
  Applying auth.0006_require_contenttypes_0002... OK
  Applying auth.0007_alter_validators_add_error_messages... OK
  Applying auth.0008_alter_user_username_max_length... OK
  Applying auth.0009_alter_user_last_name_max_length... OK
  Applying sessions.0001_initial... OK
```

The default database in our project is currently set to SQLite named `db.sqlite3`.

Creating a **django project's app**

```s
$ cd blog
$ python manage.py startapp posts
```

The project structure should look like

```s
$ find .
./posts
./posts/migrations
./posts/migrations/__init__.py
./posts/models.py
./posts/__init__.py
./posts/apps.py
./posts/admin.py
./posts/tests.py
./posts/views.py
./db.sqlite3
./blog
./blog/__init__.py
./blog/__pycache__
./blog/__pycache__/settings.cpython-36.pyc
./blog/__pycache__/wsgi.cpython-36.pyc
./blog/__pycache__/__init__.cpython-36.pyc
./blog/__pycache__/urls.cpython-36.pyc
./blog/settings.py
./blog/urls.py
./blog/wsgi.py
./manage.py
```

### 2. Model

Each model instance is a definitive source of the information about your data. In general, each model pertains to a single table in your database.

```python
# djangoapi/blog/posts/models.py
from django.db import models

# Create your models here.

class Post(models.Model):
    title = models.CharField(max_length=255)
    content = models.TextField()
    is_featured = models.BooleanField(default=False)

    def __str__(self):
        return self.name
```

> `__str__` is called by the `str()` built-in function and by the print statement to compute the "informal" string representation of an object.

If you try running `makemigrations`, django won't see those changes yet.

```s
$ No changes detected
```

To solve this, add your `posts` app to your project's installed apps.

```python
# djangoapi/blog/blog/settings.py

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'posts' # Add it here
]
```

To continue with the migration of models

```s
$ python manage.py makemigrations

Migrations for 'posts':
  posts/migrations/0001_initial.py
    - Create model Post

$ python manage.py migrate

Operations to perform:
  Apply all migrations: admin, auth, contenttypes, posts, sessions
Running migrations:
  Applying posts.0001_initial... OK

```

### 3. Serialization

Serializers allow data structure or object state to be translated into a format that can be stored or transmitted and be reconstructed later on.

Create API's `serializers.py` and `views.py` files and isolate them like this

```s
# posts/api
posts/api/serializers.py
posts/api/views.py

# posts/migrations
posts/migrations/

# posts
posts/admin.py
posts/apps.py
posts/models.py
posts/tests.py
posts/views.py
```

```python
# posts/api/serializers.py

from ..models import Post
from rest_framework import serializers

class PostSerializer(serializers.ModelSerializer):
    class Meta:
        model = Post
        fields = ('title', 'content', 'is_featured') # if not declared, all fields of the model will be shown
```

In this tutorial we have used `ModelSerializer`, more on [this](http://www.django-rest-framework.org/api-guide/serializers/).

### 4. Views

A view function, or view for short, is a Python function that takes a Web request and returns a Web response.

```python
# posts/api/views.py

from ..models import Post
from . import serializers
from rest_framework import generics, status
from rest_framework.response import Response

class PostListView(generics.ListAPIView):
    queryset = Post.objects.all()
    serializer_class = serializers.PostSerializer
```

As seen above, `ListAPIView` is used for read-only endpoints to represent a collection of model instances.

In this code snippet, we use `generics` view methods from the `rest_framework`, more on [this](http://www.django-rest-framework.org/api-guide/generic-views/).

###### 5. URLs

This is where we setup our routes or URL paths to our designated views in which we expect specific responses for each.

```python
# posts/urls.py

from django.urls import path
from . import views
from .api import views

urlpatterns = [
    path('', views.PostListView.as_view(), name=None)
]
```

### 6. Finalizing Setup

Ensure that the `rest_framework` is added to our project's apps.

```python
# djangoapi/blog/blog/settings.py

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'rest_framework', # Add it here
    'posts'
]
```

### 7. Django Admin

Since we haven't setup our `POST` requests yet, we will be populating the database through django's admin panel.

To do that, create a superuser account `admin` with password `1234password`.

```s
$ python manage.py createsuperuser --email admin@example.com --username admin

Password:
Password (again):
This password is too common.
Bypass password validation and create user anyway? [y/N]: y
Superuser created successfully.
```

Register the model in the admin panel.

```python
# posts/admin.py

from django.contrib import admin
from .models import Post

# Register your models here.
admin.site.register(Post)
```

That's it. Visit the [admin panel](http://127.0.0.1:8000/admin/) and update `posts` model's records. More on [this](https://docs.djangoproject.com/en/2.1/ref/contrib/admin/).

### 8. Testing our API

```s
$ python manage.py runserver
```

```s
GET /api/v1/posts/
```

```json
HTTP 200 OK
Allow: GET, HEAD, OPTIONS
Content-Type: application/json
Vary: Accept

[
    {
        "title": "Example Post #1",
        "content": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
        "is_featured": false
    },
    {
        "title": "Example Post #2",
        "content": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
        "is_featured": true
    }
]
```

Great. Now it's time for us to update our views and finish the standard CRUD operations.

### 9. Adding more views

`POST` is a method used for creating (sometimes updating) a resource in the database.

```python
# posts/api/views.py

from ..models import Post
from . import serializers
from rest_framework import generics, status
from rest_framework.response import Response

class PostCreateView(generics.CreateAPIView):
    queryset = Post.objects.all()
    serializer_class = serializers.PostSerializer

    def create(self, request, *args, **kwargs):
        super(PostCreateView, self).create(request, args, kwargs)
        response = {"status_code": status.HTTP_200_OK,
                    "message": "Successfully created",
                    "result": request.data}
        return Response(response)
```

Most often, we separate `List` and `Create` view classes when we want to expose a list of data set while *easily preventing* a certain request to `POST` or create a resource in the database for that specific `List` view.

Usecase always varies for apps, you are opt to use [ListCreateAPIView](http://www.django-rest-framework.org/api-guide/generic-views/#listcreateapiview) or even [ViewSets](http://www.django-rest-framework.org/api-guide/viewsets/) for combining the logic for a set of related views.

*Optional*: Since we want to display the data in a more systematic way, we override `create` method and map our inline custom response handler.

Adding more views with methods `GET`, `PATCH`, `DELETE` to handle a specific blog post detail.

```python
class PostDetailView(generics.RetrieveUpdateDestroyAPIView):
    queryset = Post.objects.all()
    serializer_class = serializers.PostSerializer

    def retrieve(self, request, *args, **kwargs):
        super(PostDetailView, self).retrieve(request, args, kwargs)
        instance = self.get_object()
        serializer = self.get_serializer(instance)
        data = serializer.data
        response = {"status_code": status.HTTP_200_OK,
                    "message": "Successfully retrieved",
                    "result": data}
        return Response(response)

    def patch(self, request, *args, **kwargs):
        super(PostDetailView, self).patch(request, args, kwargs)
        instance = self.get_object()
        serializer = self.get_serializer(instance)
        data = serializer.data
        response = {"status_code": status.HTTP_200_OK,
                    "message": "Successfully updated",
                    "result": data}
        return Response(response)

    def delete(self, request, *args, **kwargs):
        super(PostDetailView, self).delete(request, args, kwargs)
        response = {"status_code": status.HTTP_200_OK,
                    "message": "Successfully deleted"}
        return Response(response)
```

### 10. Updating URLs

```python
# posts/urls.py

from django.urls import path
from . import views
from .api import views

urlpatterns = [
    path('', views.PostListView.as_view(), name=None),
    path('create/', views.PostCreateView.as_view(), name=None),
    path('<int:pk>/', views.PostDetailView.as_view(), name=None)
]
```

Now you can send requests to your API via [Postman](https://www.getpostman.com/), your app or do a `GET` requests from your browser, examples:

```s
POST /api/v1/posts/create/
```

```json
HTTP 200 OK
Allow: POST, OPTIONS
Content-Type: application/json
Vary: Accept

{
    "status_code": 200,
    "message": "Successfully created",
    "result": {
        "csrfmiddlewaretoken": "rnSUN3XOIghnXA0yKghnQgxg0do39xhorYene5ALw3gWGThK5MjG6YjL8VUb7v2h",
        "title": "Creating a resource",
        "content": "Howdy mate!"
    }
}
```

```s
GET /api/v1/posts/1/
```

```json
HTTP 200 OK
Allow: GET, PUT, PATCH, DELETE, HEAD, OPTIONS
Content-Type: application/json
Vary: Accept

{
    "status_code": 200,
    "message": "Successfully retrieved",
    "result": {
        "title": "Sample Post",
        "content": "Sample Post Content",
        "is_featured": false
    }
}
```

That's it. You have successfully managed to develop RESTful APIs with DRF! Cheers!

# Source code

Available on [GitHub](https://github.com/joshuadeguzman/djangoapi-demo).

# Bonus
I'll cover another article covering Testable RESTful API with RESTful Authentication using [JSON Web Tokens](https://jwt.io/) and include a link on here soon, stay tuned!

For inquiries or suggestions, feel free to comment or contact [me](twitter.com/devjdg).