Introduction to API with Django
Introduction to API with Django

Today I'll talk about how to setup a basic API with Django. In the process, I'll give step-by-step instructions of how to create a simple To Do app. You can see an example of this done on this repository. Also we'll go over the basics of how to protect the endpoints through the use of a JWT token. Let's get started!

Setting Up

Before you start make sure you have python and pip installed (we are using python 3).

To handle our dependencies in an isolated manner we are using virtual environments. To set it up on Ubuntu first install virtualenv module:

python3 -m pip install --user virtualenv

Then run the command below. This will create a folder that will hold all the dependencies for that environment. We'll create ours in a folder named python-environments located in our home folder but you can place it wherever you like.

> cd ~/python-environments
> python3 -m venv django-api

Now, you need to activate this virtual environment by running the command below. This command will vary according to where you choose to create the your environment. In our case:

source ~/python-environments/django-api/bin/activate

Once you activate the environment you can access the python command directly without having to type python3 and any modules you install using pip will be placed within the folder we created.

Installing Django

Next we'll setup Django and the admin panel. The first step is to install the Django package through pip. With our venv activated run the command:

python -m pip install Django

If all goes well you'll get the version number when running the command:

python -m django --version

Creating a Project

To create a blank Django project run the command below. It will create a folder with the name you defined for the project and all the necessary files. We'll call our project djangoapi. Next cd into the folder you just created.

> django-admin startproject djangoapi
> cd djangoapi

Creating a Module for the App

Within this folder there will another folder with same name [djangoapi]. This is the main module and contains some key configuration files we'll get to in a moment. You could create your logic withing this folder, however it's a common practice to create one or more modules for your application in order to keep things organized. Run the command below to create a module called todo where we'll put the logic for our todo app.

> python manage.py startapp todo

Here the contents of our project so far.

Next we need to tell Django about the new module we just created. We do this through configuring the value of INSTALLED_APPS in the settings file located in the main module. This file contains all the settings for the project. Add the the value todo.apps.TodoConfig to the installed apps settings like so:

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'todo.apps.TodoConfig'
]

Notice that this string refers to the TodoConfig class in the file djangoapi/todo/apps.py.

Running the Application

A few more things before we can run the project. Django automatically manages the database so we don't need to create any tables manually. Run the command below to prepare the database for use. For this tutorial we are using a basic file based database that comes pre-configured with Django. However, these steps are same regardless of whether you use Postgres or any other solution.

> python manage.py migrate

Also, to access the admin panel generated by Django you must create an admin user. This can be easily done by running the command below and following the prompts.

> python manage.py createsuperuser

All done! Run this command to run the project:

> python manage.py runserver

Now access http://127.0.0.1:8000/admin in your browser and login with credentials of the superuser you've created. You'll be greeted by the following screen:

This is the famous Django admin panel, a module that automatically generates an admin panel based on the models you define. This can be highly customized and is often used by staff members to manage content of an application. Django already comes with models for users and groups that we can use in our API. Once we add our own models they'll also be listed here.

Creating our Model

We'll create a model for our to do item. We'll place it in the models file in the module we created for our app (djangoapi/todo/models.py).

from django.db import models


class Todo(models.Model):
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
    name = models.CharField(max_length=20)
    completed = models.BooleanField(default=False)

The attributes created_at and updated_at are timestamps automatically managed by Django. Django will use these files to create and query the database. You may create as many models as you'd like and would probably need to split this file into a file for each model as they grow in size. Django Models are very powerful and can handle many different cases and their documentation is a great source of information.

In order for our new models to be picked up by the admin panel we need to adjust the file djangoapi/todo/admin.py. In this file we can configure how our module will behave and look in the admin panel. Here is the content of that file after the adjustment:

from django.contrib import admin

from .models import Todo

admin.site.register(Todo)

Lastly, we need to tell Django to adjust the database to accommodate the new models:

> python manage.py makemigrations
> python manage.py migrate

All done! Our model is now fully configured. It is now listed in the admin panel and we can now create, edit and remove to do items directly from here.

Configuring the API

We'll use a module called Django rest framework to make things a little easier. First we need to install the necessary packages.

> pip install djangorestframework
> pip install markdown
> pip install django-filter

Next, adjust the settings file to include rest_framework as an installed app like so:

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'todo.apps.TodoConfig',
    'rest_framework'
]

Creating the Endpoints

For our example API we will create three endpoints: (a) one to create a to do item; (b) one to edit a to do and (c) one to view a to do item. The url's for these endpoints will be:

- POST /api/todos
- GET /api/todos/<id>
- PUT /api/todos/<id>

The endpoints with POST and PUT will accept two body parameters: (a) the name of the to do item and (b) whether or not it is completed.

We'll created the logic for our endpoints in the views file within our module (djangoapi/todo/views.py). We'll extend the APIView class from the rest_framework module and create a function for each of the methods we'd like to implement. To implement our three endpoints our views would look like this:

from django.http import JsonResponse
from rest_framework.views import APIView

from .models import Todo

class TodosView(APIView):

    def post(self, request):
        todo_name = request.data.get('name', '')
        completed = request.data.get('completed', '')
        
        todo = Todo(
            name=todo_name,
            completed=completed.lower() in ['True', 'true']
        )
        todo.save()

        return JsonResponse({
            "id": todo.id,
            "name": todo.name,
            "completed": todo.completed
        })

    def get(self, request, id):
        todo = Todo.objects.get(id=id)
        return JsonResponse({
            "id": todo.id,
            "name": todo.name,
            "completed": todo.completed
        })


    def put(self, request, id):
        todo_name = request.data.get('name', '')
        completed = request.data.get('completed', '')

        todo = Todo.objects.get(id=id)
        todo.completed = completed.lower() in ['True', 'true']
        todo.name = todo_name
        todo.save()
        
        return JsonResponse({
            "id": todo.id,
            "name": todo.name,
            "completed": todo.completed
        })

    

Notice that any data passing in the body of the request will be available in the object request.data. Parameters passed as segments of the url are passed in as arguments.

Configuring the Url

The last step is to map the routes and the views we have created. To do this, we edit the urls file in the main module (located at: djangoapi/djangoapi/urls.py). To configure the three endpoints we need for our APP our url file would look like this:

from django.contrib import admin
from django.urls import path

from todo.views import TodosView

urlpatterns = [
    path('admin/', admin.site.urls),
    path("api/todos", TodosView.as_view()),
    path("api/todos/<int:id>", TodosView.as_view()),
]

Now Django knows that when it gets requests to "api/todos" and "api/todos/<id>" it will let our views handle the process.

Security

Let's quickly add a layer of security to our endpoints otherwise anyone can access them. For that will use this module that integrates with Django rest framework. To install it:

> pip install djangorestframework-simplejwt

Now for Django rest to work properly with this module we need to add the following to the settings file:

REST_FRAMEWORK = {
    "DEFAULT_PERMISSION_CLASSES": [
        "rest_framework.permissions.IsAuthenticated",
    ],
    "DEFAULT_AUTHENTICATION_CLASSES": 	     ["rest_framework_simplejwt.authentication.JWTAuthentication"]    
}

The first part (DEFAULT_PERMISSION_CLASSES) defines a rule where every endpoint will return 401 (unauthorized) unless the user is authenticated. The second property (DEFAULT_AUTHENTICATION_CLASSES) defines the module just installed as the one responsible for for authentication. From here on out, the requests will only be accepted if the contain an Authorization header with a proper token.

Generating the Token

The Simple JWT module already provides an endpoint to generate a token. To get this to work we have to make a adjustment to the url file. Here's how it should look:

from django.contrib import admin
from django.urls import path
from rest_framework_simplejwt.views import (
    TokenObtainPairView,
    TokenRefreshView,
)

from todo.views import TodosView

urlpatterns = [
    path('admin/', admin.site.urls),
    path("api/todos", TodosView.as_view()),
    path("api/todos/<int:id>", TodosView.as_view()),
    path('api/token', TokenObtainPairView.as_view(), name='token_obtain_pair'),
    path('api/token/refresh', TokenRefreshView.as_view(), name='token_refresh')
]

Now, if you make a request to 'http://127.0.0.1:8000/api/token' passing in the credentials of the admin user you created in the body of the request you'll get a refresh and a access token.

Now add a header in the format 'Authorization: Bearer <ACCESS_TOKEN>' to your requests and we are all set! Requests without this token will be denied. This will work with tokens generated with credentials of new users created in the admin panel as well!

One more thing, while inside the root folder run the command below to generate a list of python dependencies.

> pip freeze > requirements.txt

And that concludes the end of this post! I hope you found this to be useful 😎. In case you would like to get in touch: linkedin.