Test Driven Development of a Django RESTful API

Test Driven Development of a Django RESTful API

This post walks through the process of developing a CRUD-based RESTful API with Django and Django REST Framework, which is used for rapidly building RESTful APIs based on Django models.

This post walks through the process of developing a CRUD-based RESTful API with Django and Django REST Framework, which is used for rapidly building RESTful APIs based on Django models.

This application uses:

  • Python v3.6.0
  • Django v1.11.0
  • Django REST Framework v3.6.2
  • Postgres v9.6.1
  • Psycopg2 v2.7.1
Objectives

By the end of this tutorial you will be able to…

  1. Discuss the benefits of using Django REST Framework for bootstrapping the development of a RESTful API
  2. Validate model querysets using serializers
  3. Appreciate Django REST Framework’s Browsable API feature for a cleaner and well-documented version of your APIs
  4. Practice test-driven development
Why Django REST Framework?

Django REST Framework (REST Framework) provides a number of powerful features out-of-the-box that go well with idiomatic Django, including:

  1. Browsable API: Documents your API with a human-friendly HTML output, providing a beautiful form-like interface for submitting data to resources and fetching from them using the standard HTTP methods.
  2. Auth Support: REST Framework has rich support for various authentication protocols along with permissions and throttling policies which can be configured on a per-view basis.
  3. Serializers: Serializers are an elegant way of validating model querysets/instances and converting them to native Python datatypes that can be easily rendered into JSON and XML.
  4. Throttling: Throttling is way to determine whether a request is authorized or not and can be integrated with different permissions. It is generally used for rate limiting API requests from a single user.

Plus, the documentation is easy to read and full of examples. If you’re building a RESTful API where you have a one-to-one relationship between your API endpoints and your models, then REST Framework is the way to go.

Django Project Setup

Create and activate a virtualenv:

$ mkdir django-puppy-store
$ cd django-puppy-store
$ python3.6 -m venv env
$ source env/bin/activate

Install Django and set up a new project:

(env)$ pip install django==1.11.0
(env)$ django-admin startproject puppy_store

Your current project structure should look like this:

└── puppy_store
    ├── manage.py
    └── puppy_store
        ├── __init__.py
        ├── settings.py
        ├── urls.py
        └── wsgi.py

Django App and REST Framework Setup

Start by creating the puppies app and installing REST Framework inside your virtualenv:

(env)$ cd puppy_store
(env)$ python manage.py startapp puppies
(env)$ pip install djangorestframework==3.6.2

Now we need to configure our Django project to make use of REST Framework.

First, add the puppies app and rest_framework to the INSTALLED_APPS section within puppy_store/puppy_store/settings.py:

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

Next, define global settings for REST Framework in a single dictionary, again, in the settings.py file:

REST_FRAMEWORK = {
    # Use Django's standard `django.contrib.auth` permissions,
    # or allow read-only access for unauthenticated users.
    'DEFAULT_PERMISSION_CLASSES': [],
    'TEST_REQUEST_DEFAULT_FORMAT': 'json'
}

This allows unrestricted access to the API and sets the default test format to JSON for all requests.

NOTE: Unrestricted access is fine for local development, but in a production environment you may need to restrict access to certain endpoints. Make sure to update this. Review the docs for more information.
Your current project structure should now look like:

└── puppy_store
    ├── manage.py
    ├── puppies
    │   ├── __init__.py
    │   ├── admin.py
    │   ├── apps.py
    │   ├── migrations
    │   │   └── __init__.py
    │   ├── models.py
    │   ├── tests.py
    │   └── views.py
    └── puppy_store
        ├── __init__.py
        ├── settings.py
        ├── urls.py
        └── wsgi.py

Database and Model Setup

Let’s set up the Postgres database and apply all the migrations to it.

NOTE: Feel free to swap out Postgres for the relational database of your choice!
Once you have a working Postgres server on your system, open the Postgres interactive shell and create the database:

$ psql
# CREATE DATABASE puppy_store_drf;
CREATE DATABASE
# \q

Install psycopg2 so that we can interact with the Postgres server via Python:

(env)$ pip install psycopg2==2.7.1

Update the database configuration in settings.py, adding the appropriate username and password:

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': 'puppy_store_drf',
        'USER': '<your-user>',
        'PASSWORD': '<your-password>',
        'HOST': '127.0.0.1',
        'PORT': '5432'
    }
}

Next, define a puppy model with some basic attributes in django-puppy-store/puppy_store/puppies/models.py:

from django.db import models


class Puppy(models.Model):
    """
    Puppy Model
    Defines the attributes of a puppy
    """
    name = models.CharField(max_length=255)
    age = models.IntegerField()
    breed = models.CharField(max_length=255)
    color = models.CharField(max_length=255)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    def get_breed(self):
        return self.name + ' belongs to ' + self.breed + ' breed.'

    def __repr__(self):
        return self.name + ' is added.'

Now apply the migration:

(env)$ python manage.py makemigrations
(env)$ python manage.py migrate

Sanity Check

Hop into psql again and verify that the puppies_puppy has been created:

$ psql
# \c puppy_store_drf
You are now connected to database "puppy_store_drf".
puppy_store_drf=# \dt
                      List of relations
 Schema |            Name            | Type  |     Owner
--------+----------------------------+-------+----------------
 public | auth_group                 | table | michael.herman
 public | auth_group_permissions     | table | michael.herman
 public | auth_permission            | table | michael.herman
 public | auth_user                  | table | michael.herman
 public | auth_user_groups           | table | michael.herman
 public | auth_user_user_permissions | table | michael.herman
 public | django_admin_log           | table | michael.herman
 public | django_content_type        | table | michael.herman
 public | django_migrations          | table | michael.herman
 public | django_session             | table | michael.herman
 public | puppies_puppy              | table | michael.herman
(11 rows)

NOTE: You can run \d+ puppies_puppy if you want to look at the actual table details.
Before moving on, let’s write a quick unit test for the Puppy model.

Add the following code to a new file called test_models.py in a new folder called “tests” within “django-puppy-store/puppy_store/puppies”:

from django.test import TestCase
from ..models import Puppy


class PuppyTest(TestCase):
    """ Test module for Puppy model """

    def setUp(self):
        Puppy.objects.create(
            name='Casper', age=3, breed='Bull Dog', color='Black')
        Puppy.objects.create(
            name='Muffin', age=1, breed='Gradane', color='Brown')

    def test_puppy_breed(self):
        puppy_casper = Puppy.objects.get(name='Casper')
        puppy_muffin = Puppy.objects.get(name='Muffin')
        self.assertEqual(
            puppy_casper.get_breed(), "Casper belongs to Bull Dog breed.")
        self.assertEqual(
            puppy_muffin.get_breed(), "Muffin belongs to Gradane breed.")

In the above test, we added dummy entries into our puppy table via the setUp() method from django.test.TestCase and asserted that the get_breed() method returned the correct string.

Add an init.py file to “tests” and remove the tests.py file from “django-puppy-store/puppy_store/puppies”.

Let’s run our first test:

(env)$ python manage.py test
Creating test database for alias 'default'...
.
----------------------------------------------------------------------
Ran 1 test in 0.007s

OK
Destroying test database for alias 'default'...

Great! Our first unit test has passed!

Serializers

Before moving on to creating the actual API, let’s define a serializer for our Puppy model which validates the model querysets and produces Pythonic data types to work with.

Add the following snippet to django-puppy-store/puppy_store/puppies/serializers.py:

from rest_framework import serializers
from .models import Puppy


class PuppySerializer(serializers.ModelSerializer):
    class Meta:
        model = Puppy
        fields = ('name', 'age', 'breed', 'color', 'created_at', 'updated_at')

In the above snippet we defined a ModelSerializer for our puppy model, validating all the mentioned fields. In short, if you have a one-to-one relationship between your API endpoints and your models - which you probably should if you’re creating a RESTful API - then you can use a ModelSerializer to create a Serializer.

With our database in place, we can now start building the RESTful API…

RESTful Structure

In a RESTful API, endpoints (URLs) define the structure of the API and how end users access data from our application using the HTTP methods - GET, POST, PUT, DELETE. Endpoints should be logically organized around collections and elements, both of which are resources.

In our case, we have one single resource, puppies, so we will use the following URLS - /puppies/ and /puppies/<id> for collections and elements, respectively:

Routes and Testing (TDD)

We will be taking a test-first approach rather than a thorough test-driven approach, wherein we will be going through the following process:

  • add a unit test, just enough code to fail
  • then update the code to make it pass the test.

Once the test passes, start over with the same process for the new test.

Begin by creating a new file, django-puppy-store/puppy_store/puppies/tests/test_views.py, to hold all the tests for our views and create a new test client for our app:

import json
from rest_framework import status
from django.test import TestCase, Client
from django.urls import reverse
from ..models import Puppy
from ..serializers import PuppySerializer


# initialize the APIClient app
client = Client()

Before starting with all the API routes, let’s first create a skeleton of all view functions that return empty responses and map them with their appropriate URLs within the django-puppy-store/puppy_store/puppies/views.py file:

from rest_framework.decorators import api_view
from rest_framework.response import Response
from rest_framework import status
from .models import Puppy
from .serializers import PuppySerializer


@api_view(['GET', 'DELETE', 'PUT'])
def get_delete_update_puppy(request, pk):
    try:
        puppy = Puppy.objects.get(pk=pk)
    except Puppy.DoesNotExist:
        return Response(status=status.HTTP_404_NOT_FOUND)

    # get details of a single puppy
    if request.method == 'GET':
        return Response({})
    # delete a single puppy
    elif request.method == 'DELETE':
        return Response({})
    # update details of a single puppy
    elif request.method == 'PUT':
        return Response({})


@api_view(['GET', 'POST'])
def get_post_puppies(request):
    # get all puppies
    if request.method == 'GET':
        return Response({})
    # insert a new record for a puppy
    elif request.method == 'POST':
        return Response({})

Create the respective URLs to match the views in django-puppy-store/puppy_store/puppies/urls.py:

from django.conf.urls import url
from . import views


urlpatterns = [
    url(
        r'^api/v1/puppies/(?P<pk>[0-9]+)
Update *django-puppy-store/puppy_store/puppy_store/urls.py* as well:

from django.conf.urls import include, url
from django.contrib import admin

urlpatterns = [
url(r'^', include('puppies.urls')),
url(
r'^api-auth/',
include('rest_framework.urls', namespace='rest_framework')
),
url(r'^admin/', admin.site.urls),
]

## Browsable API

With all routes now wired up with the view functions, let’s open up REST Framework’s Browsable API interface and verify whether all the URLs are working as expected.

First, fire up the development server:

(env)$ python manage.py runserver


Make sure to comment out all the attributes in ```REST_FRAMEWORK``` section of our ```settings.py``` file, to bypass login. Now visit ```<a href="http://localhost:8000/api/v1/puppies" target="_blank">http://localhost:8000/api/v1/puppies</a>```

You will see an interactive HTML layout for the API response. Similarly we can test the other URLs and verify all URLs are working perfectly fine.

Let’s start with our unit tests for each route.



## Routes
### GET ALL

Start with a test to verify the fetched records:

class GetAllPuppiesTest(TestCase):
""" Test module for GET all puppies API """

def setUp(self):
    Puppy.objects.create(
        name='Casper', age=3, breed='Bull Dog', color='Black')
    Puppy.objects.create(
        name='Muffin', age=1, breed='Gradane', color='Brown')
    Puppy.objects.create(
        name='Rambo', age=2, breed='Labrador', color='Black')
    Puppy.objects.create(
        name='Ricky', age=6, breed='Labrador', color='Brown')

def test_get_all_puppies(self):
    # get API response
    response = client.get(reverse('get_post_puppies'))
    # get data from db
    puppies = Puppy.objects.all()
    serializer = PuppySerializer(puppies, many=True)
    self.assertEqual(response.data, serializer.data)
    self.assertEqual(response.status_code, status.HTTP_200_OK)

Run the test. You should see the following error:

self.assertEqual(response.data, serializer.data)
AssertionError: {} != [OrderedDict([('name', 'Casper'), ('age',[687 chars])])]


Update the view to get the test to pass.

@api_view(['GET', 'POST'])
def get_post_puppies(request):
# get all puppies
if request.method == 'GET':
puppies = Puppy.objects.all()
serializer = PuppySerializer(puppies, many=True)
return Response(serializer.data)
# insert a new record for a puppy
elif request.method == 'POST':
return Response({})


Here, we get all the records for puppies and validate each using the ```PuppySerializer```.

Run the tests to ensure they all pass:

Ran 2 tests in 0.072s

OK

### GET Single

Fetching a single puppy involves two test cases:
1. Get valid puppy - e.g., the puppy exists
1. Get invalid puppy - e.g., the puppy does not exists

Add the tests:

class GetSinglePuppyTest(TestCase):
""" Test module for GET single puppy API """

def setUp(self):
    self.casper = Puppy.objects.create(
        name='Casper', age=3, breed='Bull Dog', color='Black')
    self.muffin = Puppy.objects.create(
        name='Muffin', age=1, breed='Gradane', color='Brown')
    self.rambo = Puppy.objects.create(
        name='Rambo', age=2, breed='Labrador', color='Black')
    self.ricky = Puppy.objects.create(
        name='Ricky', age=6, breed='Labrador', color='Brown')

def test_get_valid_single_puppy(self):
    response = client.get(
        reverse('get_delete_update_puppy', kwargs={'pk': self.rambo.pk}))
    puppy = Puppy.objects.get(pk=self.rambo.pk)
    serializer = PuppySerializer(puppy)
    self.assertEqual(response.data, serializer.data)
    self.assertEqual(response.status_code, status.HTTP_200_OK)

def test_get_invalid_single_puppy(self):
    response = client.get(
        reverse('get_delete_update_puppy', kwargs={'pk': 30}))
    self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)

Run the tests. You should see the following error:

self.assertEqual(response.data, serializer.data)
AssertionError: {} != {'name': 'Rambo', 'age': 2, 'breed': 'Labr[109 chars]26Z'}


Update the view:

@api_view(['GET', 'UPDATE', 'DELETE'])
def get_delete_update_puppy(request, pk):
try:
puppy = Puppy.objects.get(pk=pk)
except Puppy.DoesNotExist:
return Response(status=status.HTTP_404_NOT_FOUND)

# get details of a single puppy
if request.method == 'GET':
    serializer = PuppySerializer(puppy)
    return Response(serializer.data)

In the above snippet, we get the puppy using an ID. Run the tests to ensure they all pass.



### POST

Inserting a new record involves two cases as well:
1. Inserting a valid puppy
1. Inserting a invalid puppy

First, write tests for it:

class CreateNewPuppyTest(TestCase):
""" Test module for inserting a new puppy """

def setUp(self):
    self.valid_payload = {
        'name': 'Muffin',
        'age': 4,
        'breed': 'Pamerion',
        'color': 'White'
    }
    self.invalid_payload = {
        'name': '',
        'age': 4,
        'breed': 'Pamerion',
        'color': 'White'
    }

def test_create_valid_puppy(self):
    response = client.post(
        reverse('get_post_puppies'),
        data=json.dumps(self.valid_payload),
        content_type='application/json'
    )
    self.assertEqual(response.status_code, status.HTTP_201_CREATED)

def test_create_invalid_puppy(self):
    response = client.post(
        reverse('get_post_puppies'),
        data=json.dumps(self.invalid_payload),
        content_type='application/json'
    )
    self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)

Run the tests. You should see two failures:

self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
AssertionError: 200 != 400

self.assertEqual(response.status_code, status.HTTP_201_CREATED)
AssertionError: 200 != 201


Again, update the view to get the tests to pass:

@api_view(['GET', 'POST'])
def get_post_puppies(request):
# get all puppies
if request.method == 'GET':
puppies = Puppy.objects.all()
serializer = PuppySerializer(puppies, many=True)
return Response(serializer.data)
# insert a new record for a puppy
if request.method == 'POST':
data = {
'name': request.data.get('name'),
'age': int(request.data.get('age')),
'breed': request.data.get('breed'),
'color': request.data.get('color')
}
serializer = PuppySerializer(data=data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)


Here, we inserted a new record by serializing and validating the request data before inserting to the database.

Run the tests again to ensure they pass.

You can also test this out with the Browsable API. Fire up the development server again, and navigate to [http://localhost:8000/api/v1/puppies/](http://localhost:8000/api/v1/puppies/ "http://localhost:8000/api/v1/puppies/"). Then, within the POST form, submit the following as ```application/json```:

{
"name": "Muffin",
"age": 4,
"breed": "Pamerion",
"color": "White"
}


Be sure the GET ALL and Get Single work as well.



### PUT

Start with a test to update a record. Similar to adding a record, we again need to test for both valid and invalid updates:

class UpdateSinglePuppyTest(TestCase):
""" Test module for updating an existing puppy record """

def setUp(self):
    self.casper = Puppy.objects.create(
        name='Casper', age=3, breed='Bull Dog', color='Black')
    self.muffin = Puppy.objects.create(
        name='Muffy', age=1, breed='Gradane', color='Brown')
    self.valid_payload = {
        'name': 'Muffy',
        'age': 2,
        'breed': 'Labrador',
        'color': 'Black'
    }
    self.invalid_payload = {
        'name': '',
        'age': 4,
        'breed': 'Pamerion',
        'color': 'White'
    }

def test_valid_update_puppy(self):
    response = client.put(
        reverse('get_delete_update_puppy', kwargs={'pk': self.muffin.pk}),
        data=json.dumps(self.valid_payload),
        content_type='application/json'
    )
    self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)

def test_invalid_update_puppy(self):
    response = client.put(
        reverse('get_delete_update_puppy', kwargs={'pk': self.muffin.pk}),
        data=json.dumps(self.invalid_payload),
        content_type='application/json')
    self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)

Run the tests.

self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
AssertionError: 405 != 400

self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
AssertionError: 405 != 204


Update the view:

@api_view(['GET', 'DELETE', 'PUT'])
def get_delete_update_puppy(request, pk):
try:
puppy = Puppy.objects.get(pk=pk)
except Puppy.DoesNotExist:
return Response(status=status.HTTP_404_NOT_FOUND)

# get details of a single puppy
if request.method == 'GET':
    serializer = PuppySerializer(puppy)
    return Response(serializer.data)

# update details of a single puppy
if request.method == 'PUT':
    serializer = PuppySerializer(puppy, data=request.data)
    if serializer.is_valid():
        serializer.save()
        return Response(serializer.data, status=status.HTTP_204_NO_CONTENT)
    return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

# delete a single puppy
elif request.method == 'DELETE':
    return Response({})

In the above snippet, similar to an insert, we serialize and validate the request data and then respond appropriately.

Run the tests again to ensure that all the tests pass.



### DELETE

To delete a single record, an ID is required:

class DeleteSinglePuppyTest(TestCase):
""" Test module for deleting an existing puppy record """

def setUp(self):
    self.casper = Puppy.objects.create(
        name='Casper', age=3, breed='Bull Dog', color='Black')
    self.muffin = Puppy.objects.create(
        name='Muffy', age=1, breed='Gradane', color='Brown')

def test_valid_delete_puppy(self):
    response = client.delete(
        reverse('get_delete_update_puppy', kwargs={'pk': self.muffin.pk}))
    self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)

def test_invalid_delete_puppy(self):
    response = client.delete(
        reverse('get_delete_update_puppy', kwargs={'pk': 30}))
    self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)

Run the tests. You should see:

self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
AssertionError: 200 != 204


Update the view:

@api_view(['GET', 'DELETE', 'PUT'])
def get_delete_update_puppy(request, pk):
try:
puppy = Puppy.objects.get(pk=pk)
except Puppy.DoesNotExist:
return Response(status=status.HTTP_404_NOT_FOUND)

# get details of a single puppy
if request.method == 'GET':
    serializer = PuppySerializer(puppy)
    return Response(serializer.data)

# update details of a single puppy
if request.method == 'PUT':
    serializer = PuppySerializer(puppy, data=request.data)
    if serializer.is_valid():
        serializer.save()
        return Response(serializer.data, status=status.HTTP_204_NO_CONTENT)
    return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

# delete a single puppy
if request.method == 'DELETE':
    puppy.delete()
    return Response(status=status.HTTP_204_NO_CONTENT)

Run the tests again. Make sure all of them pass. Make sure to test out the UPDATE and DELETE functionality within the Browsable API as well!

Thanks for reading ❤

If you liked this post, share it with all of your programming buddies!

Follow me on [**Facebook**](https://www.facebook.com/moriohdotcom "**Facebook**") | [**Twitter**](https://twitter.com/moriohdotcom "**Twitter**")
### Learn More

☞ [Python and Django Full Stack Web Developer Bootcamp](http://learnstartup.net/p/r1-quFMgce "Python and Django Full Stack Web Developer Bootcamp")

☞ [Build a Backend REST API with Python & Django - Advanced](http://learnstartup.net/p/VyGrb6hcg "Build a Backend REST API with Python & Django - Advanced")

☞ [Python Django Dev To Deployment](http://learnstartup.net/p/9yr7V_-O8 "Python Django Dev To Deployment")

☞ [Django Core | A Reference Guide to Core Django Concepts](http://learnstartup.net/p/S1eqVBxZg "Django Core | A Reference Guide to Core Django Concepts")

☞ [Build Your First Python and Django Application ](http://dev.edupioneer.net/1935fcab7e%22%22 "Build Your First Python and Django Application ")

☞ [Flask Or Django? An In-Depth Comparison](http://on.morioh.net/108ddbf9b5 "Flask Or Django? An In-Depth Comparison")

☞ [How To Set Up Django with Postgres, Nginx, and Gunicorn on Ubuntu 16.04](http://on.geeklearn.net/73b1fadaaf "How To Set Up Django with Postgres, Nginx, and Gunicorn on Ubuntu 16.04")

☞ [Building a Universal Application with Nuxt.js and Django](http://on.geeklearn.net/0bda45c395 "Building a Universal Application with Nuxt.js and Django")

☞ [Django Tutorial: Building and Securing Web Applications](http://on.morioh.net/be6171a7c3 "Django Tutorial: Building and Securing Web Applications")

*Originally published on *[*https://realpython.com*](https://realpython.com "*https://realpython.com*")
,
        views.get_delete_update_puppy,
        name='get_delete_update_puppy'
    ),
    url(
        r'^api/v1/puppies/
Update *django-puppy-store/puppy_store/puppy_store/urls.py* as well:

from django.conf.urls import include, url
from django.contrib import admin

urlpatterns = [
url(r'^', include('puppies.urls')),
url(
r'^api-auth/',
include('rest_framework.urls', namespace='rest_framework')
),
url(r'^admin/', admin.site.urls),
]

## Browsable API

With all routes now wired up with the view functions, let’s open up REST Framework’s Browsable API interface and verify whether all the URLs are working as expected.

First, fire up the development server:

(env)$ python manage.py runserver


Make sure to comment out all the attributes in ```REST_FRAMEWORK``` section of our ```settings.py``` file, to bypass login. Now visit ```<a href="http://localhost:8000/api/v1/puppies" target="_blank">http://localhost:8000/api/v1/puppies</a>```

You will see an interactive HTML layout for the API response. Similarly we can test the other URLs and verify all URLs are working perfectly fine.

Let’s start with our unit tests for each route.



## Routes
### GET ALL

Start with a test to verify the fetched records:

class GetAllPuppiesTest(TestCase):
""" Test module for GET all puppies API """

def setUp(self):
    Puppy.objects.create(
        name='Casper', age=3, breed='Bull Dog', color='Black')
    Puppy.objects.create(
        name='Muffin', age=1, breed='Gradane', color='Brown')
    Puppy.objects.create(
        name='Rambo', age=2, breed='Labrador', color='Black')
    Puppy.objects.create(
        name='Ricky', age=6, breed='Labrador', color='Brown')

def test_get_all_puppies(self):
    # get API response
    response = client.get(reverse('get_post_puppies'))
    # get data from db
    puppies = Puppy.objects.all()
    serializer = PuppySerializer(puppies, many=True)
    self.assertEqual(response.data, serializer.data)
    self.assertEqual(response.status_code, status.HTTP_200_OK)

Run the test. You should see the following error:

self.assertEqual(response.data, serializer.data)
AssertionError: {} != [OrderedDict([('name', 'Casper'), ('age',[687 chars])])]


Update the view to get the test to pass.

@api_view(['GET', 'POST'])
def get_post_puppies(request):
# get all puppies
if request.method == 'GET':
puppies = Puppy.objects.all()
serializer = PuppySerializer(puppies, many=True)
return Response(serializer.data)
# insert a new record for a puppy
elif request.method == 'POST':
return Response({})


Here, we get all the records for puppies and validate each using the ```PuppySerializer```.

Run the tests to ensure they all pass:

Ran 2 tests in 0.072s

OK

### GET Single

Fetching a single puppy involves two test cases:
1. Get valid puppy - e.g., the puppy exists
1. Get invalid puppy - e.g., the puppy does not exists

Add the tests:

class GetSinglePuppyTest(TestCase):
""" Test module for GET single puppy API """

def setUp(self):
    self.casper = Puppy.objects.create(
        name='Casper', age=3, breed='Bull Dog', color='Black')
    self.muffin = Puppy.objects.create(
        name='Muffin', age=1, breed='Gradane', color='Brown')
    self.rambo = Puppy.objects.create(
        name='Rambo', age=2, breed='Labrador', color='Black')
    self.ricky = Puppy.objects.create(
        name='Ricky', age=6, breed='Labrador', color='Brown')

def test_get_valid_single_puppy(self):
    response = client.get(
        reverse('get_delete_update_puppy', kwargs={'pk': self.rambo.pk}))
    puppy = Puppy.objects.get(pk=self.rambo.pk)
    serializer = PuppySerializer(puppy)
    self.assertEqual(response.data, serializer.data)
    self.assertEqual(response.status_code, status.HTTP_200_OK)

def test_get_invalid_single_puppy(self):
    response = client.get(
        reverse('get_delete_update_puppy', kwargs={'pk': 30}))
    self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)

Run the tests. You should see the following error:

self.assertEqual(response.data, serializer.data)
AssertionError: {} != {'name': 'Rambo', 'age': 2, 'breed': 'Labr[109 chars]26Z'}


Update the view:

@api_view(['GET', 'UPDATE', 'DELETE'])
def get_delete_update_puppy(request, pk):
try:
puppy = Puppy.objects.get(pk=pk)
except Puppy.DoesNotExist:
return Response(status=status.HTTP_404_NOT_FOUND)

# get details of a single puppy
if request.method == 'GET':
    serializer = PuppySerializer(puppy)
    return Response(serializer.data)

In the above snippet, we get the puppy using an ID. Run the tests to ensure they all pass.



### POST

Inserting a new record involves two cases as well:
1. Inserting a valid puppy
1. Inserting a invalid puppy

First, write tests for it:

class CreateNewPuppyTest(TestCase):
""" Test module for inserting a new puppy """

def setUp(self):
    self.valid_payload = {
        'name': 'Muffin',
        'age': 4,
        'breed': 'Pamerion',
        'color': 'White'
    }
    self.invalid_payload = {
        'name': '',
        'age': 4,
        'breed': 'Pamerion',
        'color': 'White'
    }

def test_create_valid_puppy(self):
    response = client.post(
        reverse('get_post_puppies'),
        data=json.dumps(self.valid_payload),
        content_type='application/json'
    )
    self.assertEqual(response.status_code, status.HTTP_201_CREATED)

def test_create_invalid_puppy(self):
    response = client.post(
        reverse('get_post_puppies'),
        data=json.dumps(self.invalid_payload),
        content_type='application/json'
    )
    self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)

Run the tests. You should see two failures:

self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
AssertionError: 200 != 400

self.assertEqual(response.status_code, status.HTTP_201_CREATED)
AssertionError: 200 != 201


Again, update the view to get the tests to pass:

@api_view(['GET', 'POST'])
def get_post_puppies(request):
# get all puppies
if request.method == 'GET':
puppies = Puppy.objects.all()
serializer = PuppySerializer(puppies, many=True)
return Response(serializer.data)
# insert a new record for a puppy
if request.method == 'POST':
data = {
'name': request.data.get('name'),
'age': int(request.data.get('age')),
'breed': request.data.get('breed'),
'color': request.data.get('color')
}
serializer = PuppySerializer(data=data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)


Here, we inserted a new record by serializing and validating the request data before inserting to the database.

Run the tests again to ensure they pass.

You can also test this out with the Browsable API. Fire up the development server again, and navigate to [http://localhost:8000/api/v1/puppies/](http://localhost:8000/api/v1/puppies/ "http://localhost:8000/api/v1/puppies/"). Then, within the POST form, submit the following as ```application/json```:

{
"name": "Muffin",
"age": 4,
"breed": "Pamerion",
"color": "White"
}


Be sure the GET ALL and Get Single work as well.



### PUT

Start with a test to update a record. Similar to adding a record, we again need to test for both valid and invalid updates:

class UpdateSinglePuppyTest(TestCase):
""" Test module for updating an existing puppy record """

def setUp(self):
    self.casper = Puppy.objects.create(
        name='Casper', age=3, breed='Bull Dog', color='Black')
    self.muffin = Puppy.objects.create(
        name='Muffy', age=1, breed='Gradane', color='Brown')
    self.valid_payload = {
        'name': 'Muffy',
        'age': 2,
        'breed': 'Labrador',
        'color': 'Black'
    }
    self.invalid_payload = {
        'name': '',
        'age': 4,
        'breed': 'Pamerion',
        'color': 'White'
    }

def test_valid_update_puppy(self):
    response = client.put(
        reverse('get_delete_update_puppy', kwargs={'pk': self.muffin.pk}),
        data=json.dumps(self.valid_payload),
        content_type='application/json'
    )
    self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)

def test_invalid_update_puppy(self):
    response = client.put(
        reverse('get_delete_update_puppy', kwargs={'pk': self.muffin.pk}),
        data=json.dumps(self.invalid_payload),
        content_type='application/json')
    self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)

Run the tests.

self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
AssertionError: 405 != 400

self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
AssertionError: 405 != 204


Update the view:

@api_view(['GET', 'DELETE', 'PUT'])
def get_delete_update_puppy(request, pk):
try:
puppy = Puppy.objects.get(pk=pk)
except Puppy.DoesNotExist:
return Response(status=status.HTTP_404_NOT_FOUND)

# get details of a single puppy
if request.method == 'GET':
    serializer = PuppySerializer(puppy)
    return Response(serializer.data)

# update details of a single puppy
if request.method == 'PUT':
    serializer = PuppySerializer(puppy, data=request.data)
    if serializer.is_valid():
        serializer.save()
        return Response(serializer.data, status=status.HTTP_204_NO_CONTENT)
    return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

# delete a single puppy
elif request.method == 'DELETE':
    return Response({})

In the above snippet, similar to an insert, we serialize and validate the request data and then respond appropriately.

Run the tests again to ensure that all the tests pass.



### DELETE

To delete a single record, an ID is required:

class DeleteSinglePuppyTest(TestCase):
""" Test module for deleting an existing puppy record """

def setUp(self):
    self.casper = Puppy.objects.create(
        name='Casper', age=3, breed='Bull Dog', color='Black')
    self.muffin = Puppy.objects.create(
        name='Muffy', age=1, breed='Gradane', color='Brown')

def test_valid_delete_puppy(self):
    response = client.delete(
        reverse('get_delete_update_puppy', kwargs={'pk': self.muffin.pk}))
    self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)

def test_invalid_delete_puppy(self):
    response = client.delete(
        reverse('get_delete_update_puppy', kwargs={'pk': 30}))
    self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)

Run the tests. You should see:

self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
AssertionError: 200 != 204


Update the view:

@api_view(['GET', 'DELETE', 'PUT'])
def get_delete_update_puppy(request, pk):
try:
puppy = Puppy.objects.get(pk=pk)
except Puppy.DoesNotExist:
return Response(status=status.HTTP_404_NOT_FOUND)

# get details of a single puppy
if request.method == 'GET':
    serializer = PuppySerializer(puppy)
    return Response(serializer.data)

# update details of a single puppy
if request.method == 'PUT':
    serializer = PuppySerializer(puppy, data=request.data)
    if serializer.is_valid():
        serializer.save()
        return Response(serializer.data, status=status.HTTP_204_NO_CONTENT)
    return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

# delete a single puppy
if request.method == 'DELETE':
    puppy.delete()
    return Response(status=status.HTTP_204_NO_CONTENT)

Run the tests again. Make sure all of them pass. Make sure to test out the UPDATE and DELETE functionality within the Browsable API as well!

Thanks for reading ❤

If you liked this post, share it with all of your programming buddies!

Follow me on [**Facebook**](https://www.facebook.com/moriohdotcom "**Facebook**") | [**Twitter**](https://twitter.com/moriohdotcom "**Twitter**")
### Learn More

☞ [Python and Django Full Stack Web Developer Bootcamp](http://learnstartup.net/p/r1-quFMgce "Python and Django Full Stack Web Developer Bootcamp")

☞ [Build a Backend REST API with Python & Django - Advanced](http://learnstartup.net/p/VyGrb6hcg "Build a Backend REST API with Python & Django - Advanced")

☞ [Python Django Dev To Deployment](http://learnstartup.net/p/9yr7V_-O8 "Python Django Dev To Deployment")

☞ [Django Core | A Reference Guide to Core Django Concepts](http://learnstartup.net/p/S1eqVBxZg "Django Core | A Reference Guide to Core Django Concepts")

☞ [Build Your First Python and Django Application ](http://dev.edupioneer.net/1935fcab7e%22%22 "Build Your First Python and Django Application ")

☞ [Flask Or Django? An In-Depth Comparison](http://on.morioh.net/108ddbf9b5 "Flask Or Django? An In-Depth Comparison")

☞ [How To Set Up Django with Postgres, Nginx, and Gunicorn on Ubuntu 16.04](http://on.geeklearn.net/73b1fadaaf "How To Set Up Django with Postgres, Nginx, and Gunicorn on Ubuntu 16.04")

☞ [Building a Universal Application with Nuxt.js and Django](http://on.geeklearn.net/0bda45c395 "Building a Universal Application with Nuxt.js and Django")

☞ [Django Tutorial: Building and Securing Web Applications](http://on.morioh.net/be6171a7c3 "Django Tutorial: Building and Securing Web Applications")

*Originally published on *[*https://realpython.com*](https://realpython.com "*https://realpython.com*")
,
        views.get_post_puppies,
        name='get_post_puppies'
    )
]

Update django-puppy-store/puppy_store/puppy_store/urls.py as well:

from django.conf.urls import include, url
from django.contrib import admin

urlpatterns = [
    url(r'^', include('puppies.urls')),
    url(
        r'^api-auth/',
        include('rest_framework.urls', namespace='rest_framework')
    ),
    url(r'^admin/', admin.site.urls),
]

Browsable API

With all routes now wired up with the view functions, let’s open up REST Framework’s Browsable API interface and verify whether all the URLs are working as expected.

First, fire up the development server:

(env)$ python manage.py runserver

Make sure to comment out all the attributes in REST_FRAMEWORK section of our settings.py file, to bypass login. Now visit <a href="http://localhost:8000/api/v1/puppies" target="_blank">http://localhost:8000/api/v1/puppies</a>

You will see an interactive HTML layout for the API response. Similarly we can test the other URLs and verify all URLs are working perfectly fine.

Let’s start with our unit tests for each route.

Routes

GET ALL

Start with a test to verify the fetched records:

class GetAllPuppiesTest(TestCase):
    """ Test module for GET all puppies API """

    def setUp(self):
        Puppy.objects.create(
            name='Casper', age=3, breed='Bull Dog', color='Black')
        Puppy.objects.create(
            name='Muffin', age=1, breed='Gradane', color='Brown')
        Puppy.objects.create(
            name='Rambo', age=2, breed='Labrador', color='Black')
        Puppy.objects.create(
            name='Ricky', age=6, breed='Labrador', color='Brown')

    def test_get_all_puppies(self):
        # get API response
        response = client.get(reverse('get_post_puppies'))
        # get data from db
        puppies = Puppy.objects.all()
        serializer = PuppySerializer(puppies, many=True)
        self.assertEqual(response.data, serializer.data)
        self.assertEqual(response.status_code, status.HTTP_200_OK)

Run the test. You should see the following error:

self.assertEqual(response.data, serializer.data)
AssertionError: {} != [OrderedDict([('name', 'Casper'), ('age',[687 chars])])]

Update the view to get the test to pass.

@api_view(['GET', 'POST'])
def get_post_puppies(request):
    # get all puppies
    if request.method == 'GET':
        puppies = Puppy.objects.all()
        serializer = PuppySerializer(puppies, many=True)
        return Response(serializer.data)
    # insert a new record for a puppy
    elif request.method == 'POST':
        return Response({})

Here, we get all the records for puppies and validate each using the PuppySerializer.

Run the tests to ensure they all pass:

Ran 2 tests in 0.072s

OK

GET Single

Fetching a single puppy involves two test cases:

  1. Get valid puppy - e.g., the puppy exists
  2. Get invalid puppy - e.g., the puppy does not exists

Add the tests:

class GetSinglePuppyTest(TestCase):
    """ Test module for GET single puppy API """

    def setUp(self):
        self.casper = Puppy.objects.create(
            name='Casper', age=3, breed='Bull Dog', color='Black')
        self.muffin = Puppy.objects.create(
            name='Muffin', age=1, breed='Gradane', color='Brown')
        self.rambo = Puppy.objects.create(
            name='Rambo', age=2, breed='Labrador', color='Black')
        self.ricky = Puppy.objects.create(
            name='Ricky', age=6, breed='Labrador', color='Brown')

    def test_get_valid_single_puppy(self):
        response = client.get(
            reverse('get_delete_update_puppy', kwargs={'pk': self.rambo.pk}))
        puppy = Puppy.objects.get(pk=self.rambo.pk)
        serializer = PuppySerializer(puppy)
        self.assertEqual(response.data, serializer.data)
        self.assertEqual(response.status_code, status.HTTP_200_OK)

    def test_get_invalid_single_puppy(self):
        response = client.get(
            reverse('get_delete_update_puppy', kwargs={'pk': 30}))
        self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)

Run the tests. You should see the following error:

self.assertEqual(response.data, serializer.data)
AssertionError: {} != {'name': 'Rambo', 'age': 2, 'breed': 'Labr[109 chars]26Z'}

Update the view:

@api_view(['GET', 'UPDATE', 'DELETE'])
def get_delete_update_puppy(request, pk):
    try:
        puppy = Puppy.objects.get(pk=pk)
    except Puppy.DoesNotExist:
        return Response(status=status.HTTP_404_NOT_FOUND)

    # get details of a single puppy
    if request.method == 'GET':
        serializer = PuppySerializer(puppy)
        return Response(serializer.data)

In the above snippet, we get the puppy using an ID. Run the tests to ensure they all pass.

POST

Inserting a new record involves two cases as well:

  1. Inserting a valid puppy
  2. Inserting a invalid puppy

First, write tests for it:

class CreateNewPuppyTest(TestCase):
    """ Test module for inserting a new puppy """

    def setUp(self):
        self.valid_payload = {
            'name': 'Muffin',
            'age': 4,
            'breed': 'Pamerion',
            'color': 'White'
        }
        self.invalid_payload = {
            'name': '',
            'age': 4,
            'breed': 'Pamerion',
            'color': 'White'
        }

    def test_create_valid_puppy(self):
        response = client.post(
            reverse('get_post_puppies'),
            data=json.dumps(self.valid_payload),
            content_type='application/json'
        )
        self.assertEqual(response.status_code, status.HTTP_201_CREATED)

    def test_create_invalid_puppy(self):
        response = client.post(
            reverse('get_post_puppies'),
            data=json.dumps(self.invalid_payload),
            content_type='application/json'
        )
        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)

Run the tests. You should see two failures:

self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
AssertionError: 200 != 400

self.assertEqual(response.status_code, status.HTTP_201_CREATED)
AssertionError: 200 != 201

Again, update the view to get the tests to pass:

@api_view(['GET', 'POST'])
def get_post_puppies(request):
    # get all puppies
    if request.method == 'GET':
        puppies = Puppy.objects.all()
        serializer = PuppySerializer(puppies, many=True)
        return Response(serializer.data)
    # insert a new record for a puppy
    if request.method == 'POST':
        data = {
            'name': request.data.get('name'),
            'age': int(request.data.get('age')),
            'breed': request.data.get('breed'),
            'color': request.data.get('color')
        }
        serializer = PuppySerializer(data=data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

Here, we inserted a new record by serializing and validating the request data before inserting to the database.

Run the tests again to ensure they pass.

You can also test this out with the Browsable API. Fire up the development server again, and navigate to http://localhost:8000/api/v1/puppies/. Then, within the POST form, submit the following as application/json:

{
    "name": "Muffin",
    "age": 4,
    "breed": "Pamerion",
    "color": "White"
}

Be sure the GET ALL and Get Single work as well.

PUT

Start with a test to update a record. Similar to adding a record, we again need to test for both valid and invalid updates:

class UpdateSinglePuppyTest(TestCase):
    """ Test module for updating an existing puppy record """

    def setUp(self):
        self.casper = Puppy.objects.create(
            name='Casper', age=3, breed='Bull Dog', color='Black')
        self.muffin = Puppy.objects.create(
            name='Muffy', age=1, breed='Gradane', color='Brown')
        self.valid_payload = {
            'name': 'Muffy',
            'age': 2,
            'breed': 'Labrador',
            'color': 'Black'
        }
        self.invalid_payload = {
            'name': '',
            'age': 4,
            'breed': 'Pamerion',
            'color': 'White'
        }

    def test_valid_update_puppy(self):
        response = client.put(
            reverse('get_delete_update_puppy', kwargs={'pk': self.muffin.pk}),
            data=json.dumps(self.valid_payload),
            content_type='application/json'
        )
        self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)

    def test_invalid_update_puppy(self):
        response = client.put(
            reverse('get_delete_update_puppy', kwargs={'pk': self.muffin.pk}),
            data=json.dumps(self.invalid_payload),
            content_type='application/json')
        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)

Run the tests.

self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
AssertionError: 405 != 400

self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
AssertionError: 405 != 204

Update the view:

@api_view(['GET', 'DELETE', 'PUT'])
def get_delete_update_puppy(request, pk):
    try:
        puppy = Puppy.objects.get(pk=pk)
    except Puppy.DoesNotExist:
        return Response(status=status.HTTP_404_NOT_FOUND)

    # get details of a single puppy
    if request.method == 'GET':
        serializer = PuppySerializer(puppy)
        return Response(serializer.data)

    # update details of a single puppy
    if request.method == 'PUT':
        serializer = PuppySerializer(puppy, data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_204_NO_CONTENT)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

    # delete a single puppy
    elif request.method == 'DELETE':
        return Response({})

In the above snippet, similar to an insert, we serialize and validate the request data and then respond appropriately.

Run the tests again to ensure that all the tests pass.

DELETE

To delete a single record, an ID is required:

class DeleteSinglePuppyTest(TestCase):
    """ Test module for deleting an existing puppy record """

    def setUp(self):
        self.casper = Puppy.objects.create(
            name='Casper', age=3, breed='Bull Dog', color='Black')
        self.muffin = Puppy.objects.create(
            name='Muffy', age=1, breed='Gradane', color='Brown')

    def test_valid_delete_puppy(self):
        response = client.delete(
            reverse('get_delete_update_puppy', kwargs={'pk': self.muffin.pk}))
        self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)

    def test_invalid_delete_puppy(self):
        response = client.delete(
            reverse('get_delete_update_puppy', kwargs={'pk': 30}))
        self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)

Run the tests. You should see:

self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
AssertionError: 200 != 204

Update the view:

@api_view(['GET', 'DELETE', 'PUT'])
def get_delete_update_puppy(request, pk):
    try:
        puppy = Puppy.objects.get(pk=pk)
    except Puppy.DoesNotExist:
        return Response(status=status.HTTP_404_NOT_FOUND)

    # get details of a single puppy
    if request.method == 'GET':
        serializer = PuppySerializer(puppy)
        return Response(serializer.data)

    # update details of a single puppy
    if request.method == 'PUT':
        serializer = PuppySerializer(puppy, data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_204_NO_CONTENT)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

    # delete a single puppy
    if request.method == 'DELETE':
        puppy.delete()
        return Response(status=status.HTTP_204_NO_CONTENT)

Run the tests again. Make sure all of them pass. Make sure to test out the UPDATE and DELETE functionality within the Browsable API as well!

Django REST Framework Full Course For Beginners : Build REST API With Django

Django REST Framework Full Course For Beginners : Build REST API With Django

This is a Django REST Framework Full Course For Beginners, in this Django tutorial you will learn how to Build REST API With Django. What Are Restful APIs? What is Django REST Framework? Introduction to Django REST Framework. Django REST Framework Serializer. REST Framework Modal Serializer

This is a Django REST Framework Full Course For Beginners, in this video you will learn how to Build REST API With Django, basically this is a one video Course that have different parts.

What Are Restful APIs ?

A RESTful API acts as a translator between two machines communicating over a Web service. If your familiar with the concept of API This is just like an API but it’s working on a RESTful Web service and the API is used by two applications trying to communicate with each other over a network or Internet.
. Web developers program REST API that server can receive data from applications. These applications can be web-apps, Android/iOS apps, etc. RESTful APIs return JSON files which can be interpreted by a variety of devices.

What is Django REST Framework?

DRF is a framework built upon the Django Framework. And it is used for developing restful apis .
Some reasons you might want to use REST framework:
• The Web browsable API is a huge usability win for your developers.
• Authentication policies
• Serialization that supports both ORM and non-ORM data sources.
• Extensive documentation, and great community support.

Timestamp For Table Of Content

0 - Django REST Framework Introduction = 00:00:05
1 - Django Project Setup = 00:04:02
2 - Django REST Framework Serializer = 00:08:58
3 - REST Framework Modal Serializer = 00:29:25
4 - REST Framework Function Based API Views = 00:31:55
5 - REST Framework api_view() Decorator = 00:54:37
6 - REST Class Based API Views = 01:08:17
7 - REST Generic Views & Mixins = 01:24:54
8 - REST Framework Authentication = 01:40:40
9 - REST Framework Viewsets & Routers = 01:56:16
10 - REST Framework Generic Viewsets = 02:13:22
11 - REST Framework Modal Viewsets = 02:19:45

What the difference between REST API and RESTful API?

What the difference between REST API and RESTful API?

Representational state transfer (REST) is a style of software architecture. As described in a dissertation by Roy Fielding, REST is an "architectural style" that basically exploits the existing technology and protocols of the Web. RESTful is typically used to refer to web services implementing such an architecture.

The short answer is that REST stands for Representational State Transfer. It’s an architectural pattern for creating web services. A RESTful service is one that implements that pattern.

The long answer starts with “sort of” and “it depends” and continues with more complete definitions.

Defining REST

Let’s start by defining what REST is and is not. For some, REST means a server that exchanges JSON documents with a client over HTTP. Not only is that not a complete definition, but it’s also not always true. The REST specification doesn’t require HTTP or JSON. (The spec doesn’t mention JSON or XML at all.)

The Origins of REST

Roy Fielding introduced the REST architectural pattern in a dissertation he wrote in 2000. The paper defines a means for clients and servers to exchange application data. A key feature is that the client doesn’t need to know anything about the application in advance. The link is to chapter five of his paper. While the entire dissertation describes the hows and whys of REST, that chapter defines the architectural pattern.

Fielding doesn’t mandate specific requirements. Instead, he defines REST regarding constraints and architectural elements.

REST’s Architectural Constraints

Here is a summary of the constraints.

  • Client-server – REST applications have a server that manages application data and state. The server communicates with a client that handles the user interactions. A clear separation of concerns divides the two components. This means you can update and improve them in independent tracks.
  • Stateless – servers don’t maintain any client state. Clients manage their application state. Their requests to servers contain all the information required to process them.
  • Cacheable – servers must mark their responses as cacheable or not. So, infrastructures and clients can cache them when possible to improve performance. They can dispose of non-cacheable Information, so no client uses stale data.
  • Uniform interface – this constraint is REST’s most well known feature or rule, depending on who you ask. Fielding says “The central feature that distinguishes the REST architectural style from other network-based styles is its emphasis on a uniform interface between components.” REST services provide data as resources, with a consistent namespace. We’ll cover this in detail below.
  • Layered system – components in the system cannot “see” beyond their layer. So, you can easily add load-balancers and proxies to improve security or performance.

A RESTful service is more than a web server that exchanges JSON, or any other, documents. These constraints work together to create a very specific type of application.

Applying the Constraints

First, the client-server, layered systems and stateless constraints combine to form an application with solid boundaries and clear separations between concerns. Data moves from the server to the client upon request. The client displays or manipulates it. If the state changes, the client sends it back to the server for storage. Fielding specifically contrasts REST with architectures that use distributed objects to hide data from other components. In REST, the client and server share knowledge about data and state. The architecture doesn’t conceal data, it only hides implementations.

The cacheable and uniform state constraints go one step further. Application data is available to clients in a clear and consistent interface and cached when possible.

So, that’s the technical definition of REST. What does it look like in the real world?

RPC Over HTTP vs. RESTful

Often when someone says that a service “isn’t REST,” they’re looking at the URIs or how the service uses HTTP verbs. They’re referring to REST’s presentation of data as a uniform set of resources.

This distinction is sometimes framed as a difference between remote procedures calls (RPC) and REST. Imagine a web service for listing, adding, and removing, items from an e-commerce inventory.

In one version, there’s a single URL that we query with HTTP GETs or POSTs.  You interact with the service by POSTing a document, setting the contents to reflect what you want to do.

Add new items with a POST with a NewItem:

POST /inventory HTTP/1.1
 
{
    "NewItem": {
          "name": "new item",
          "price": "9.99",
          "id": "1001"
      }
}    

Query for items with a POST and an ItemRequest:

POST /inventory HTTP/1.1
 
{
    "ItemRequest": {
          "id": "1001"
      }
}

Some implementations accept a request for a new item with a get, too.

POST /inventory?id=1001 HTTP/1.1

We also change or delete items with a POST and an ItemDelete or ItemUpdate.

POST /inventory HTTP/1.1
 
{
    "ItemDelete": {
          "id": "1001"
      }
}

This isn’t REST. We’re not exchanging the state of resources. We’re calling a function with arguments that happen to be in a JSON document or URL arguments.

A RESTful service has a URI for each item in the inventory.

So, adding a new item would look like the example above.

POST /item HTTP/1.1
 
{
    "Item": {
          "name": "new item",
          "price": "9.99",
          "id": "1001"
      }
}    

But the similarities end there. Retrieving an item is always a GET:

GET /item/1001 HTTP/1.1   

Deleting is a DELETE:

DELETE /item/1001 HTTP/1.1  

Modifying an item is a PUT:

POST /inventory HTTP/1.1
 
{
    "Item": {
          "name": "new item",
          "price": "7.99",
          "id": "1001"
      }
}    

The difference is important. In REST, operations that use distinct HTTP actions. These verbs correspond directly to the activity on the data. GET, POST, PUT, DELETE and PATCH all have specific contracts. Most well-designed REST APIs also return specific HTTP codes, depending on the result of the request.

The critical point is that the URIs operate on the data, not on remote methods.

But there’s another reason why the resource model is essential.

REST vs RESTful and the Richardson Maturity Model

When you model your URIs after resources and use HTTP verbs you make your API predictable. Once developers know how you defined your resources, they can almost predict what the API looks like. Here again, the emphasis is on understanding the data, not the operations.

But even if you can’t make the API entirely predictable, you can document any REST service with hypertext. So, each item returned in the inventory app would contain links for deleting, modifying, or setting the inventory level of the resource. Fielding says that before a service is RESTful, it must provide hypertext media as part of the API.

Many sites don’t meet this requirement but are still called REST. Fact is, many sites break the rules in one way or another. So many that Leonard Richardson created a model breaks down REST into levels of compliance

We’ve already covered the source levels:

  • 0 – exporting an API over HTTP with methods called with arguments
  • 1 – Exporting resources instead of methods
  • 2 – Proper use of HTTP verbs
  • 3 – Exporting hypertext with objects that make all or part of the API discoverable.

Richardson’s model is his own, and it doesn’t map directly into Fielding’s spec. Since Fielding requires level three, he would say that most apps aren’t REST anyway.

The point is many services that we colloquially refer to as REST, technically aren’t.

REST vs RESTful: Does It Matter?

So, does the REST vs. RESTful comparison matter? Probably not. How well your architecture complies with an arbitrary standard isn’t as important with how well it suits your needs and can grow with your business.

The REST architectural pattern has many advantages. Fielding designed it for the web and, 18 years later, most of the constraints he had in mind are still with us. In 2000 we didn’t have Android or the iPhone. IE5 had 50% of the browser market share. It’s biggest rival was Firefox. But Fielding recognized what online applications needed and how web clients would evolve from HTML display engines into complete applications. The tools we use today have grown to suit REST, not the other way around.

Thank you for reading. Hope this tutorial will help you!

What is REST API? | Restful Web Service

What is REST API? | Restful Web Service

In this post "Restful Web Service", you'll learn: What is Web services, what is API, What is REST API, How REST works and Implementation of REST API

What is REST API? | Restful Web Service

A REST API defines a set of functions to process requests and responses via HTTP protocol.

REST is used in mobile application as well as in web applications.