An API (Application Program Interface) is a regulated method of providing data to other applications and services. It's a set of protocols for building and integrating application software. APIs lets your service or app communicate with other apps without having to know how they are built and implemented.

What is a RESTful API?

A RESTful API or simply REST (Representational State Transfer), is a type of API that uses HTTP requests to access data. Most REST APIs use JSON to send the payload. This is because JSON is lightweight and easy to interpret.

Some of the main types of requests in a REST API are:

  • GET -- retrieves resources from the API based on the endpoint and parameters passed
  • POST -- creates a new resource or record
  • PUT -- updates an existing resource or a record
  • DELETE -- deletes a resource or record at the given URI

Prerequisites

To follow along with this tutorial, it's good to have at least some basic understanding of:

By the end of this tutorial, you will be able to:

  • Install Django REST framework
  • Serialize Django models
  • Setup common HTTP requests
  • Install and configure Cross-Origin Resource Sharing (CORS)

Let's dive in!

Step 1 -- Installing Django

It's recommended to build Python projects in virtual environments. Let's create a virtual environment for our project. Run the command below in a terminal.

$ python3 -m venv virtual

The command creates a new virtual environment virtual in the current directory. Activate the environment by running:

$ source virtual/bin/activate

Then install the latest version of Django via PyPi using the command below.

$ pip install django

You can test Django installation in the python shell by running the command below:

$ python -m django --version

The command prints the version of your Django installation. If not installed, you will get a no module named django error.

Step 3 -- Creating a Django project

Now that there's an activated virtual environment with Django installed let's create a project. A Django project is a directory that contains all settings associated with a Django instance. This includes:

  • Database configuration
  • Language and time zone configurations
  • Middleware configurations
  • Debug configurations
  • Template configurations

Run the command below to create a new Django project.

$ django-admin startproject rest

The command creates a new folder rest in the current directory. Navigate to this folder in your terminal.

$ cd rest

The project directory tree should look like this:

.
├── rest
│   ├── manage.py
│   └── rest
│       ├── asgi.py
│       ├── __init__.py
│       ├── settings.py
│       ├── urls.py
│       └── wsgi.py

To learn more about the generated files and folders, head over to the documentation.

Step 4 -- Creating the API app.

Django comes with a utility that generates the basic folder structure of an app so that you can focus more on writing code rather than creating directories.

Run the command below to create a Django app.

$ python manage.py startapp api

The command generates a new api folder with the following folder structure:

.
├── api
│   ├── admin.py
│   ├── apps.py
│   ├── __init__.py
│   ├── migrations
│   │   ├── __init__.py
│   ├── models.py
│   ├── tests.py
│   ├── urls.py
│   └── views.py

Step 5 -- Registering the API app to the Project

Open the project directory in your code editor. Open settings.py  and add your api app to the installed apps.

rest/settings.py

INSTALLED_APPS = [
    #.....
    'api',
]

Step 6 --  Configuring URL routing

Since we'll be creating only one app inside our Django project, we can point the base route of our project to our API app. Create a file urls.py in the api folder and put the following code.

api/urls.py

from . import views
from django.urls import path

urlpatterns = [
    
]

We import the views.py file in the API directory and create an empty list of URL patterns. Let's point the project URL to these URL patterns.

Edit the urls.py file in the rest folder to look like this:

rest/urls.py

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

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

Step 7 -- Running the app

Time to run our app. Create the default database schema by running migrations using the following command.

$ python manage.py migrate

Then run the app in the local server using the following command.

$ python manage.py runserver

The app is served at localhost port 8000 by default. ie.  http://127.0.0.1:8000. You can change the localhost port by appending the port number to the command above. For example to serve the app at port 7000, run the command below.

$ python manage.py runserver 7000
Django empty project
Default Django welcome page

Step 8 -- A simple API View

Let's see what a simple API in Django looks like. Open views.py and add the following code.

api/views.py

from django.http import JsonResponse
from rest_framework.decorators import api_view

@api_view(["GET"])
def index(request):
    content = {"Message": "Hello World!"}
    return JsonResponse(content)

We import the JsonResponse class which allows us to return JSON in functions. We then create a GET function index that returns some JSON.

Let's create a URL configuration for this function. Add the following code to urls.py.

api/urls.py

#.....

urlpatterns = [
    path('', views.index)
]

The function index is directed to the base app route. Reload your browser to see the API.

A simple GET API view 

Now that we have the simplest API running, let's build a larger API. We'll build a notes API. The API will be able to:

  • Get existing notes
  • Add notes to the Database
  • Edit existing notes
  • Delete notes

Step 9 -- Installing Django REST framework

To build APIs more easily, we'll be using Django REST framework. It helps us a lot, as we'll not be hard-coding everything. Let's install it using the command below.

$ pip install djangorestframework

Then, we'll need to register the framework under installed apps in settings.py. Edit the installed apps section in settings.py to look like this:

rest/settings.py

#.....
INSTALLED_APPS = [
	#.....
    'api',
    'rest_framework', # register rest framework
]
#.....

Step 10 -- Creating a database model

Open models.py and add the following code.

api/models.py

#......
class Note(models.Model):
    title=models.CharField(max_length=200)
    body=models.TextField()
    date=models.DateField()
    
    def __str__(self):
        return self.title

The code creates a database Model Note with the title, body and date properties.

Let's create this database structure by running these 2 commands.

$ python manage.py makemigrations
$ python manage.py migrate

The makemigrations command detects the changes in your models and makes migrations accordingly. Then the migrate command applies the changes to the database.

Step 11 -- Serializing the model

A serializer is a component that will convert Django models to JSON objects and vice-versa.

Create a file serializer.py in your app directory and put the following code.

api/serializer.py

from rest_framework import serializers
from .models import Note

class NoteSerializer(serializers.ModelSerializer):
    class Meta:
        model = Note
        fields = ('id','title','body','date')

We import serializers  module from the  rest_framework  package. We then create  NoteSerializer class that inherits from the  ModelSerializer class. We then specify the model and fields we want to return. Now we need to create an API view to handle the requests.

Step 12 -- Adding some Notes

Before we write the API view, let's add some test notes. To do this, we need to:

  • register Note model to admin dashboard
  • Create a superuser
  • add notes

Open admin.py and put the following code.

rest/admin.py

from django.contrib import admin
from . import models

admin.site.register(models.Note)

The code registers the Note model to the admin dashboard. Let's create a superuser to login to the admin dashboard.

Run the following command and enter the credentials as prompted.

$ python manage.py createsuperuser

Now that you have an admin created, go to /admin route .ie. http://127.0.0.1:8000/admin and log in using the credentials you created above. Add some notes by clicking Notes and then Add Note

dashboard
Adding notes via admin dashboard

Step 13 -- Getting notes list view

Add the following code inside views.py.

api/views.py

from rest_framework.response import Response
from rest_framework.views import APIView

from .models import Note
from .serializer import NoteSerializer

#.....

class NotesList(APIView):
    def get(self, request, format=None):
        all_notes = Note.objects.all()
        serializers = NoteSerializer(all_notes,many=True)
        return Response(serializers.data)

We import the Response class from rest framework to handle API requests. We also import the APIView as a base class for our API view function. We then create NoteList class that inherits from the  APIView class. We then define a  get  method that:

  1. queries the database to get all Note objects
  2. serializes the model objects
  3. returns the serialized data as response

Let's create a new URL configuration (URLConf) to handle requests to this model.

Step 14 -- Creating API View URLConf

Edit the urlpatterns under  urls.py to look like this:

api/urls.py

urlpatterns = [
    #.....
    path('notes', views.NotesList.as_view()),
]

And now your new view is ready. Go to the /notes endpoint to view the results.

/notes endpoint on a browser
/notes endpoint in Postman

Testing the API

Testing an API is a critical thing in API building. To test this API, you can use any API testing tool or choose one from below.

  • Postman. You can download Postman here.
  • Postman chrome extension. Although this extension is deprecated, it's still working and can be useful if you want to save your RAM. You can download the extension here.
  • Rested Firefox extension. This is a Firefox extension that can be used to test APIs. You can download it here.

Step 15 -- Adding notes to the database

It's boring and illogical to be adding notes via the admin dashboard. Why don't we write some code to enable us to add notes using the API?

Under views.py add the following code.

api/views.py

from rest_framework import status
#.....
class Notes(APIView):
	#.....
	
    def post(self, request, format=None):
        serializers = NoteSerializer(data=request.data)
        if serializers.is_valid():
            serializers.save()
            return Response(serializers.data, status=status.HTTP_201_CREATED)
        return Response(serializers.data, status=status.HTTP_400_BAD_REQUEST)

We import status from rest framework to return status codes in each request. We also add a post method that:

  1. serializes input data
  2. saves it to the database if the data is valid
  3. returns a 400 status code if the data is invalid

Making a post request to the route /notes and passing the note properties will add a new note to the database.

adding note success postman
To add notes, make a post request at /notes

Step 16 -- Getting a single note

Let's say, you want to view a single note. You don't have to scroll through a long list of notes. We can write some code to get that specific note.

api/views.py

class singleNote(APIView):
    def get(self, request,pk, format=None):
        try:
            note = Note.objects.get(pk=pk)
            serializers = NoteSerializer(note)
            return Response(serializers.data)
        except Note.DoesNotExist:
            return Response(status=status.HTTP_404_NOT_FOUND)

We create another API View class singleNote. Inside it, we create a get method that:

  1. gets a note from the database by id
  2. serializes it if the note exists
  3. returns the data
  4. returns a 404 status code if the note does not exist

Let's create a URLConf for the singleNote class. Open urls.py and add the code.

api/urls.py

urlpatterns = [
	#.....
    path('notes/<pk>', views.singleNote.as_view())
]

At this point, if you make a get request at /notes/1 .ie  http://127.0.0.1:8000/notes/1 you'll get the details of the note id 1.

To get a single note make a get request at /notes/<id>

Step 17 -- Editing existing notes

Let's say we add a note and there's a typo in between. Do you always have to go to the admin dashboard? NO. We can set up another method to do that for us.

To edit existing notes, we'll need to identify the note by an id. Let's add another method inside the singleNote class.

api/views.py

class singleNote(APIView):
	#.....
	
    def put(self, request, pk, format=None):
        note = Note.objects.get(pk=pk)
        serializers = NoteSerializer(note, request.data)
            
        if serializers.is_valid():
            serializers.save()
            return Response(serializers.data)
        else:
            return Response(serializers.errors,status=status.HTTP_400_BAD_REQUEST)

The put method above:

  1. gets a note id
  2. serializes input data
  3. saves the serialized data to the identified note if the data is valid.
  4. returns a 400 status code if the input data is invalid

If you make a put request at /notes/1, the note id 1 will be updated.

To update a note make a PUT request at /notes/<id> 

Step 17 -- Deleting a note

To delete a single note, delete() method. A note is identified by id and then deleted. Let's add another function inside the singleNote class.

api/views.py

class singleNote(APIView):
	#.....
	
    def delete(self,request, pk, format=None):
        try:
            note = Note.objects.get(pk=pk)
            note.delete()
            return Response(status=status.HTTP_200_OK)
        except:
            return Response(status=status.HTTP_404_NOT_FOUND)

The delete method above:

  1. gets a note by id
  2. deletes the note
  3. returns a 200 status code on deletion
  4. returns a 404 status code if the note does not exist

Making a delete request at /notes/1 will delete note id 1.

To delete a note make a DELETE request at /notes/<id>

Cross-Origin Resource Sharing (CORS)

If your API is to be accessed by some front-end or a remote machine, you'll need to set up CORS. Django-cors-headers is a package that sets up CORS in Django. Install it via PyPI using the command below.

$ pip install django-cors-headers

Then register the package under installed apps in settings.py.

rest/settings.py

INSTALLED_APPS = [
	#.....
    'rest_framework',
    'corsheaders'
]

You'll also be required to add CORS middleware to listen in on responses.

rest/settings.py

MIDDLEWARE = [
    'corsheaders.middleware.CorsMiddleware',
    #.....
]

Finally, configure allowed origins. You can either:

  • set a list of allowed origins under CORS_ALLOWED ORIGINS
  • allow all origins by setting CORS_ALLOW_ALL_ORIGINS to True
# allowing selected origins
CORS_ALLOWED_ORIGINS = [
    "https://example.com",
    #etc...
]

# allowing all origins
CORS_ALLOW_ALL_ORIGINS = True

Returning clean JSON.

By default, Django REST framework returns an inbuilt page along with your JSON at every route. You can remove it by returning pure JSON from your view functions. To do this, you'll need to import JsonResponse from the Django http package.

api/views.py

from django.http import JsonResponse

You'll then return output using JsonResponse instead of Response from your methods. You'll also be required to set the safe parameter to False since Django doesn't allow serializing non-dictionary objects by default.

api/views.py

return JsonResponse(serializers.data, safe=False)
image showing clean json
Clean JSON returned by JsonResponse
The code in this tutorial can be found in GitHub.

Summary

We've looked at how you can set up a RESTful API in Django. REST APIs can be made with the help of the Django REST framework. It makes it easier to set up the API as you don't have to hard-code everything. We have created a simple notes API and looked at how we set up the common types of HTTP requests in Django. If CORS headers are not set up, integration with front-end services is impossible.

You've successfully subscribed to Decoded For Devs
Welcome back! You've successfully signed in.
Great! You've successfully signed up.
Your link has expired
Success! Your account is fully activated, you now have access to all content.