RESTful APIs
An API (Application Programming Interface) provides an interface for developers to interact with an application’s database. Instead of just giving someone full access to a database, an API sets up rules, permissions, and endpoints for various functionality: login, logout, reading a list of blogs, individual blog details, and so on.
The traditional way to construct a web API is via REST (Representational State Transfer), a well-established architecture for how websites can communicate with one another. Since computers communicate via the web this means using the HTTP protocol which supports a number of common “methods” (also called “verbs”) such as GET, PUT, POST, and DELETE.
There are also a host of related access codes that indicate whether a request was successful (200), redirected (301), missing (404), or worse (500).
JSON
It’s important to note that since an API is communicating with another computer the information being shared is not what would be sent for a standard web page. When your browser requests, for example, the Google homepage, it sends HTTP requests and receives HTTP responses with HTML, CSS, JavaScript, images, and so on.
An API is different. Typically we’re only interested in the data from a database. This data is often transformed into JSON format to be efficiently transmitted about. The API will also have a series of well-defined rules for how a frontend client can interact with it via a REST architecture. To register a new user the frontend framework will need access an API endpoint called, for example, /api/register
. This API endpoint contains both a specific URL route and its own set of permissions.
Setup
First create a new directory on your computer for our code. I’ll place it on the Desktop in a folder called api but you can place it anywhere. Then configure our project.
$ cd ~/Desktop
$ mkdir blogapi && cd blogapi
$ source env/Scripts/activate
$ pip install django==3.0
(blogapi) $ django-admin startproject blog_project .
(blogapi) $ python manage.py startapp posts
We’re using source/env/Scripts/activate
to activate a local environment and pip
to install Django . Then we create a new Django project called blog_project
as well as our first app posts.
We need to tell Django about the new app we have created. So make sure to add posts
to our list of INSTALLED_APPS
in the settings.py
file.
# blog_project/settings.py
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'posts',
]
Let's create our database model with basically four fields: title, content, created_at, and updated_at.
from django.db import models
class Post(models.Model):
title = models.CharField(max_length=50)
content = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
def __str__(self):
return self.title
Note that we’re also defining what the__str__
representation of the model should be which is a Django best practice.
Let's update our database by first creating a new migration file and then applying it.
(blogapi) $ python manage.py makemigrations
(blogapi) $ python manage.py migrate
To view our data in Django’s excellent built-in admin app we add Post model to it as follows.
# posts/admin.py
from django.contrib import admin
from . models import Post
admin.site.register(Post)
Then create a superuser account so we can login to the admin . Type the command below and enter all the prompts. the email prompt is optional, but the rest of the prompts is required.
(blogapi) $ python manage.py createsuperuser
Now we can start up the local web server.
(blogapi) $ python manage.py runserver
Navigate to localhost:8000/admin and login with your superuser credentials.
Click on “+ Add” button next to Posts and enter in some new content.
I created three new blog posts that you can see here.
we’re done with the Django part! we don't need to create templates and views since we are creating API. Instead it’s time to add Django Rest Framework to take care of transforming our model data into an API.
Django Rest Framework(DRF)
DRF takes care of the heavy lifting of transforming our database models into a RESTful API. There are two main steps to this process: first a serializer
is used to transform the data into JSON so it can be sent over the internet, then a View
is used to define what data is sent.
checkout this article Django REST Framework Serializer. if you don't about Django Serializer or want's to know more about Django Serializer.
First stop the local server with Control+c
and use pip
to install Django Rest Framework.
(blogapi) $ pip install djangorestframework==3.10
Then add it to the INSTALLED_APPS
section of our settings.py
file.
# blog_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',
'posts',
]
Now let's create a new serializers.py file in our posts app.
(blogapi) $ touch posts/serializers.py
Recall that the serializer is used to convert our data into JSON format. Here’s how it looks like.
# posts/serializers.py
from rest_framework import serializers
from .models import Post
class PostSerializer(serializers.ModelSerializer):
class Meta:
model = Post
fields = ('id', 'title', 'content', 'created_at', 'updated_at',)
On the top two files we’re importing serializers
from DRF and our models. Next, we create a serializer class and create a Meta
class within it. The fields
control which database attributes are available. In this case we’re exposing all our fields including id
which is the primary key Django automatically adds to all database records.
Next we need to create our views. Just as Django has generic class based views, so too DRF has generic views we can use. Let’s add a view to list all blog posts and a detail view for a specific post.
Creating the View using Class base approach
Update the views.py
file in posts
as follows.
# posts/views.py
from rest_framework import generics
from .models import Post
from .serializers import PostSerializer
class PostList(generics.ListAPIView):
queryset = Post.objects.all()
serializer_class = PostSerializer
class PostDetail(generics.RetrieveAPIView):
queryset = Post.objects.all()
serializer_class = PostSerializer
At the top we import generics
from DRF as well as our models and serializers files. Then we create two views: PostList
and PostDetail
. Both are just for GETs
however RetrieveAPIView
is for a single instance of a model. The complete list of generic views generic views is on the DRF site.
Creating the View using Function base approach
Update the views.py
file in posts
as follows.
# posts/views.py
from rest_framework import status
from rest_framework.decorators import api_view
from rest_framework.response import Response
from .models import Post
from .serializers import PostSerializer
@api_view(['GET', 'POST'])
def PostList(request):
"""
List all code snippets, or create a new snippet.
"""
if request.method == 'GET':
posts = Post.objects.all()
serializer = PostSerializer(posts, many=True)
return Response(serializer.data)
elif request.method == 'POST':
serializer = PostSerializer(data=request.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)
@api_view(['GET', 'PUT', 'DELETE'])
def PostDetail(request, pk):
"""
Retrieve, update or delete a code snippet.
"""
try:
post = Post.objects.get(pk=pk)
except Snippet.DoesNotExist:
return Response(status=status.HTTP_404_NOT_FOUND)
if request.method == 'GET':
serializer = PostSerializer(post,many=False)
return Response(serializer.data)
elif request.method == 'PUT':
serializer = PostSerializer(post, data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
elif request.method == 'DELETE':
post.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
The PostList() function is capable of processing two HTTP verbs – GET and POST.
If the verb is GET, the code retrieves all the Post instances.
but, If the verb is POST, the code creates a new Post. Here the data is provided in JSON format while composing the HTTP request.
The PostDetail() function is capable of processing three HTTP verbs – GET, PUT, and DELETE. If the verb is GET, then the code retrieves a single Post instance based on the primary key. If the verb is PUT, the code updates the instance and save it to the database, and if it is DELETE, then the code deletes the instance from the database.
The final piece is urls. We need to create the url routes–known as endpoints in an API–where the data is available.
Start at the project-level urls.py
file.
# blog_project/urls.py
from django.contrib import admin
from django.urls import include, path
urlpatterns = [
path('admin/', admin.site.urls),
path('api/', include('posts.urls')),
]
We’ve added include
to the second line of imports and then created a path called api/
for our posts
app.
Next we create our posts
app urls.py
file.
(blogapi) $ touch posts/urls.py
And then include the code below.
# posts/urls.py
from django.urls import path
from .views import PostList,PostDetail
urlpatterns = [
path('', PostList.as_view()),
path('<int:pk>/', PostDetail.as_view()),
]
Note the above url.py
is for View created with the class base approach. for urls.py
created with function base approach, include the code below.
# posts/urls.py
from django.urls import path
from .views import PostList,PostDetail
urlpatterns = [
path('', PostList,name = 'post-list'),
path('<int:pk>/', PostDetail,name = 'post-detail'),
]
All blog routes will be at api/
so our PostList
which has the empty string ''
will be at api/
and postDetail
at api/#
where #
represents the primary key of the entry. For example, the first blog post has a primary id of 1 so it will be at the route api/1
, the second post at api/2
, and so on.
Browsable API
Time to view our work and check out a DRF killer feature. Start up the server.
Then go to http://127.0.0.1:8000/api/.
Check out that! The api/
endpoint displays all three of my blog posts in JSON format. It also shows in the header that only GET, HEAD, OPTIONS
are allowed. No POST
ing of data.
Now go to http://127.0.0.1:8000/api/1/ and you’ll see only the data for the first entry.
Implementing CRUD
I promised at the beginning of the article that this tutorial would cover not just reading/getting content but the full CRUD syntax. Watch how easy DRF makes it to transform our API into one that supports CRUD!
Open up the posts/views.py
file and change class PostDetail(generics.RetrieveAPIView)
to class PostDetail(generics.RetrieveUpdateDestroyAPIView)
.
Now refresh the page at http://127.0.0.1:8000/api/1/ and you can see updates in our graphical UI.
You can use the “Delete” button to delete content, “Put” to either update content, and “Get” to retrieve it as before. For example, navigate to the URL endpoint for our third post: http://127.0.0.1:8000/api/3/.
Then use the graphical interface for PUT at the bottom of the page to create a new entry.
And you can GET it after making the change: