Build a Basic Rest API with the Django Rest Framework

Build a Basic Rest API with the Django Rest Framework

Disclosure: scottyfullstack.com is a participant in the Amazon Services LLC Associates Program, an affiliate advertising program designed to provide a means for sites to earn advertising fees by advertising and linking to amazon.com.

Scotty Parlor

July 16, 2020

Read Time 15 min

                

I recently published a new video about a job post by Cybercoders  that requires REST API experience. In that tutorial, I point learners to my docker repository for a django rest api, scottyfullstack/basic-rest-api.

In this post, I'd like to cover how I made that and point out some important features of the Django Rest Framework.


So what is a RESTful api?

"REST is acronym for REpresentational State Transfer. It is architectural style for distributed hypermedia systems and was first presented by Roy Fielding in 2000 in his famous dissertation."

In our use case and so many others, there is typically a front end application that is decoupled from the backend services (db, apis, etc...). A REST api presents json data that a front end app consumes and presents in user friendly way.

To start us off easy, we are going to only be using GET, POST, and DELETE methods in this tutorial.



To start, lets make a directory in our projects called rest-api.

mkdir rest-api

First, lets create a virtualenv for our development in our terminal and activate it (I use the fish shell so if you use bash don't include ".fish")

virtualenv drf
source drf/bin/activate.fish

Now let's pip install our project requirements:

Django==3.0.8
django-cors-headers==3.4.0
django-filter==2.3.0
django-rest-framework==0.1.0
djangorestframework==3.11.0
psycopg2==2.8.5
psycopg2-binary==2.8.2
dj-database-url==0.5.0

Once those are complete lets fire up a django project, cd into it and start a project called "product".

django-admin startproject restapi
cd restapi/
django-admin startapp product

And now your project should look like this:

.
├── manage.py
├── product
│ ├── admin.py
│ ├── apps.py
│ ├── __init__.py
│ ├── migrations
│ │ └── __init__.py
│ ├── models.py
│ ├── tests.py
│ └── views.py
└── restapi
├── asgi.py
├── __init__.py
├── settings.py
├── urls.py
└── wsgi.py

3 directories, 13 files


Configuring the settings.py


Go ahead and open up the settings.py file found in the main project folder restapi. Most of these will stay as defaults.

First, lets make sure we have the imports needed. OS should already be included by default, and we will be using a library for our postgres database called dj_database_url:

import os
import dj_database_url

Since we are going to be running this app on an EC2 instance and may decide to run DEBUG mode (not to be used for a real production instance), we will set ALLOWED_HOSTS to anything.

ALLOWED_HOSTS = ['*']

We will need to add what we pip installed earlier as well as product to the INSTALLED_APPS settings.

# restapi/restapi/settings.py

INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'rest_framework',
'products',
'django_filters',
'corsheaders'
]

Next, make sure your CORS middleware is added and you allow all origins (You don't always want to do this in the real world).

MIDDLEWARE = [
...
'corsheaders.middleware.CorsMiddleware',
'django.middleware.common.CommonMiddleware',
]

CORS_ORIGIN_ALLOW_ALL = True

The database is going to be tricky depending on how you approach this in your practicing. In the video tutorial, I set up an RDS postgres instance that we would connect to in this file.

We will make a hybrid database setting to account for if we are running locally with sqlite or passing a db string.

hence the 'DATABASE_URL' variable being verified.
if os.environ.get('DATABASE_URL'):
DATABASES = {
'default': dj_database_url.config(default=os.environ.get('DATABASE_URL'))
}
else:
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
}
}

and then you want to open up your rest framework permissions.

!!! We are allowing GET, POST, and DELETE here. DO NOT DO THIS ON A REAL WEB APP. This is strictly for our quick api testing and proving we understand it to employers. We are disabling Authentication to POST and DELETE to our site. You've been warned !!!



REST_FRAMEWORK = {
'DEFAULT_FILTER_BACKENDS': ('django_filters.rest_framework.DjangoFilterBackend',),
'DEFAULT_PERMISSION_CLASSES': ['rest_framework.permissions.AllowAny'],
'UNAUTHENTICATED_USER': None,
}

Finally, let's make sure we set the STATIC_ROOT somewhere in the file for our API styles that are included with Django Rest Framework.

STATIC_ROOT = 'static'

That's all for our settings.py file. Again, we are not using a hosted database here. We are using the sqlite db file to store our temp data. So when you delete the container the data will be wiped.


Product Setup: Serializers, Model, Views, and URL


Head over to your product app folder (restapi/product/*)

Open the models.py file and add your fields. In this case we are just going to have title, description, and  price.

# restapi/product/models.py

from django.db import models

class Product(models.Model):
title = models.CharField(max_length=255)
description = models.TextField()
price = models.CharField(max_length=255)

def __str__(self):
return self.title

Now make a new directory call api inside the product dir and create the following files. (restapi/product/api/)

mkdir api
cd api/
touch __init__.py urls.py serializers.py views.py

Let's start with the serializers.py file.

According to the official documentation:

"Serializers allow complex data such as querysets and model instances to be converted to native Python datatypes that can then be easily rendered into JSON, XML or other content types. Serializers also provide deserialization, allowing parsed data to be converted back into complex types, after first validating the incoming data."

In ordinary terms, this serializer is going to take our model data and turn it into a json object for our frontend to consume.

# restapi/product/api/serializer.py

from rest_framework import serializers
from product.models import Product

class ProductSerializer(serializers.ModelSerializer):
class Meta:
model = Product
fields = ('__all__')

Not too much going on here. As you can see we plug our Product model right into the serializer format.

Next, open the view.py file and add the following:

from rest_framework.generics import ListCreateAPIView, RetrieveDestroyAPIView
from products.models import Product
from .serializers import ProductSerializer

class ProductListView(ListCreateAPIView):
queryset = Product.objects.all()
serializer_class = ProductSerializer

class ProductDetailView(RetrieveDestroyAPIView):
queryset = Product.objects.all()
serializer_class = ProductSerializer
filter_fields = ('id','title')

Here we have two views. The first one, ProductListView, is a list of all our model objects. The second one, ProductDetailView, is a detailed list for a single object by 'id' (note the filter_fields). The detail view will be instrumental for our DELETE method.

Finally, in this directory we will need to populate the urls.py.

from django.urls import path
from .views import ProductDetailView, ProductListView

urlpatterns = [
path('', ProductListView.as_view()),
path('<pk>', ProductDetailView.as_view()),
]

This configuration consists of two paths, one for our list view and another for the detail.  the <pk> will be our object 'id'.

And that's it for our product api.

We have one more change to make to make this run. We have to update the urls.py in the main project folder ( ./restapi/restapi/urls.py).

# ./restapi/restapi/urls.py

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


urlpatterns = [
path('admin/', admin.site.urls),
path('api/', include('product.api.urls')),
]
As you can see above, we added the 'api/' route that includes the urls.py file we create in '/product/api/'

We can now run our app but will need to make and run migrations.

python3 manage.py makemigrations
python3 manage.py migrate
python3 manage.py runserver

Pull up http://localhost:8000/api and try it out. There is an html form for you to test with or you can always use Postman.



Extra Curricular


If you are interested in dockerizing this to play with here is an example requirements.txt, Dockerfile (with entrypoint file) and docker-compose.yml file that I use for this app.

# ./restapi/requirements.txt

Django==3.0.8
django-cors-headers==3.4.0
django-filter==2.3.0
django-rest-framework==0.1.0
djangorestframework==3.11.0
gunicorn==20.0.4

#entrypoint.py

python3 manage.py collectstatic
python3 manage.py migrate

# ./restapi/Dockerfile

FROM python:3
ENV PYTHONUNBUFFERED 1
RUN mkdir /code
WORKDIR /code
COPY requirements.txt /code/
RUN pip3 install -r requirements.txt
COPY entrypoint.sh /code/
COPY . /code/
RUN ./entrypoint.sh
EXPOSE 8000

CMD ["python3", "manage.py", "runserver", "0.0.0.0:8000"]


# ./restapi/docker-compose.yml

version: '3'
services:
web:
build: .
command: bash -c "python manage.py makemigrations && python manage.py migrate && python manage.py runserver 0.0.0.0:8000"
volumes:
- .:/code
ports:
- "8000:8000"

That's all i've got. Happy coding and remember, if you would like updates join the newsletter and slack channel of the home page for updates and if you have any questions!

Until next time,

Scotty