In this tutorial we’ll create a Django To Do app, add an API with Django Rest Framework, and then add user authentication to our API with django-rest-auth. Although you should use a custom user model for all Django projects, we will not here in the interests of simplicity. I’ll cover how to use a custom user model with Django Rest Framework auth in a future post.

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 can be found 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-rest-auth && cd drf-rest-auth
$ pipenv install django
$ pipenv shell
(drf-rest-auth) $ django-admin startproject demo_project .
(drf-rest-auth) $ 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',
]

Let’s do our first migration to set up the initial database at this point.

(drf-rest-auth) $ 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-rest-auth) $ python manage.py makemigrations todos
(drf-rest-auth) $ python manage.py migrate todos

The final step is 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)

And…we’re done. We can now create a superuser account and log into the admin for the first time.

(drf-rest-auth) $ python manage.py createsuperuser
(drf-rest-auth) $ 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

In a normal Django app at this point we would 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 need to! Instead we can jump right into using Django Rest Framework at this point.

Django Rest Framework

To start we need to install Django Rest Framework and then create a new api app.

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

Next add both rest_framework and the api app to our INSTALLED_APPS setting. We also add default permissions so that anyone can access our API. In the real world you would never do this: setting permissions is quite granular, but for demoing purposes we’ll just leave everything open. This 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',
    ]
}

Every API endpoint we create needs a serializer, url, and view. The serializer transforms our Django model data into a format that can be transmitted over the web, usually as JSON. The view applies logic to our data, and the url controls the routing.

So while a traditional Django app uses urls, views, and templates, a DRF app uses urls, views, and serializers. This will make more sense in a minute.

Within our api app create a serializers.py file and also a dedicated urls.py file.

(drf-rest-auth) $ touch api/serializers.py api/urls.py

Update our project-level urls.py file by adding include on the second line and adding an API route. Here we’re putting everything at api/v1/. It’s a best practice to version your API since it will likely change in the future. This way a future version can be at api/v2/ without breaking anything.

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

In my experience the concept of serializers is the hardest to internalize for beginners. I like to think of a normal Django project as one contained thing that outputs webpages. But an API sends out just data via API endpoints, so there’s no HTML, CSS, JavaScript or anything else transmitted. It’s just pure data in JSON format. Under the hood DRF handles most of this for us.

# 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

Here we’ve created a dedicated serializer for our ToDo model and specified the fields we want exposed. This includes the id field Django automatically adds to every database model, title, and description. We could limit the fields here–you often don’t want to expose everything–but for now we won’t.

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 to create just a ListView and DetailView for our API.

# 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

Finally we need to update the urls.py file in our our api app to display our two views. The ListView will be available at api/v1 and the DetailView will be at api/v1/1 for the first post with an id of 1, at api/v1/2 for the second post with an id of 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! Now start up the local server and we can see another cool part of Django Rest Framework: its browsable API.

(drf-rest-auth) $ python manage.py runserver

The ListView 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

To reinforce the idea that data is being sent by our API via JSON we can also use curl from the command line. In a new command line console–we want to keep our server running so it can process requests–run curl http://localhost:8000/api/v1/ to see a raw JSON view of the ListView endpoint.

$ curl http://localhost:8000/api/v1/
[{"id":1,"title":"1st todo","description":"Learn DRF."},{"id":2,"title":"2nd item","description":"Learn Python too."}]

We can also view the endpoint for a DetailView with curl http://localhost:8000/api/v1/1/. Make sure to include a closing / here or the request won’t work.

curl http://localhost:8000/api/v1/1/
{"id":1,"title":"1st todo","description":"Learn DRF."}

And we can POST a new item too with curl -X POST http://localhost:8000/api/v1/ -d "title=hello world&description=hi there".

$ curl -X POST http://localhost:8000/api/v1/ -d "title=hello world&description=hi there"
{"id":3,"title":"hello world","description":"hi there"}

If you look over at the command line tab running the server you’ll see a 201 response which tells us the POST request was successful.

[14/Mar/2018 10:44:43] "POST /api/v1/ HTTP/1.1" 201 55

Using the built-in DRF web browser is a much better experience than the command line for testing but it’s useful to know that you can use many different tools–curl, Postman–to explore your API.

User Authentication

On the authentication section of the Django Rest Framework website there are many different approaches mentioned. Without getting into a detailed discussion of their respective pros/cons, you’re best bet is probably to use TokenAuthentication if there’s any chance your API will connect with non-web clients like a native desktop or iOS/Android app.

The third-party django-rest-auth library provides a set of pre-configured API endpoints for login, logout, registration, and can support social authentication too. We’ll use it here.

First step is to install django-rest-auth and add it to our INSTALLED_APPS.

(drf-rest-auth) $ pipenv install django-rest-auth
# demo_project/settings.py
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'rest_framework',
    'rest_framework.authtoken', # new!
    'rest_auth', # new!
    'api',
    'todos',
]

Note that we’ve added two lines here: both rest_framework.authtoken which is Django Rest Framework’s token auth app and also rest_auth which uses it.

Then update our api/urls.py file. Make sure to add include as a top-line import and then the rest_auth package at rest-auth.

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

from . import views

urlpatterns = [
    path('', views.ListTodo.as_view()),
    path('<int:pk>/', views.DetailTodo.as_view()),
    path('rest-auth/', include('rest_auth.urls')),
]

And that’s literally it. Now we can migrate our changes and spin up the server to see what django-rest-auth has provided.

(drf-rest-auth) $ python manage.py migrate
(drf-rest-auth) $ python manage.py runserver

We have a working login endpoint at http://127.0.0.1:8000/api/v1/rest-auth/login/.

Log in

And a logout endpoint at http://127.0.0.1:8000/api/v1/rest-auth/logout/.

Log out

On the login endpoint page, at the bottom, use your superuser credentials and click on the “POST” button.

Login credentials

The result is a token for the user which can be used to authenticate future HTTP requests!

Login token

If you navigate over to the admin section again at http://127.0.0.1:8000/admin/ you can see there’s now an additional AUTH TOKEN section.

Admin token

Click on it and you’ll see the token–named a KEY–for the only user. You can create new users within the Admin section, access the Login API endpoint, and it will generate a unique token for each new user.

Token

Next Steps

django-rest-auth also provides support for user registration, password reset/confirm/change, and even social media authentication. I’ll try to cover these in a future post too.

However for now with a small amount of code we’ve built a Django app, added the Django Rest Framework for API endpoints, and then used django-rest-auth to include authentication. Not bad!




Interested in learning more Django? I’ve written 3 books!