With the rise of Single-Page-Applications and the trend of separating monoliths into services with distinct front and backends, knowing how to make your own RESTful API for your backend is more important than ever.

Officially, a RESTful API is a Representational State Transfer-ful Application Programming Interface. Big words, but what it really boils down to is putting data onto your web server in a way that’s accessible to other servers and clients, and it works through HTTP requests and responses and carefully structured URL routes to represent specific resource(s).

It looks a lot like this:

HTTP is short for Hypertext Transfer Protocol and it’s a set of rules that dictate how data is packaged and communicated throughout the web. There are other protocols that go along with HTTP, but HTTP will be the focus here, for simplicity.

An HTTP request looks something like this:



User-Agent: Mozilla/4.0 (compatible; MSIE5.01; Windows NT)

Host:

Content-Type: application/x-www-form-urlencoded

Content-Length: length

Accept-Language: en-us

Accept-Encoding: gzip, deflate

Connection: Keep-Alive POST /cgi-bin/process.cgi HTTP/1.1User-Agent: Mozilla/4.0 (compatible; MSIE5.01; Windows NT)Host: www.tutorialspoint.com Content-Type: application/x-www-form-urlencodedContent-Length:Accept-Language: en-usAccept-Encoding: gzip, deflateConnection: Keep-Alive licenseID=string&content=string&/paramsXML=string https://www.tutorialspoint.com/http/http_requests.htm

And an HTTP response looks something like this:

HTTP/1.1 200 OK

Date: Mon, 27 Jul 2009 12:28:53 GMT

Server: Apache/2.2.14 (Win32)

Last-Modified: Wed, 22 Jul 2009 19:15:56 GMT

Content-Length: 88

Content-Type: text/html

Connection: Closed <html>

<body>

<h1>Hello, World!</h1>

</body>

</html> https://www.tutorialspoint.com/http/http_responses.htm

Clients send HTTP requests to the server and the server sends back HTTP responses. Many things can fit within requests and responses, though most of the times the data is either metadata about the request/response or some kind of JSON string or both, when dealing with APIs.

This is a brief walkthrough of how to make a barebones API for a ‘Todo-list’ application using the Django Rest Framework.

This is not a comprehensive guide, so you should be somewhat familiar with making AJAX calls with JavaScript and Django itself. You can make an API in any modern language and the concepts behind it are similar but I am choosing Python because:

I know it

It’s clean and explicit

Django and Django Rest Framework (DRF) are both mature, stable, and well-documented

Django and DRF gives you a lot out of the box (pluggable auth systems, serializers, ORMs, Views…)

Highly customizable on every level (DRF)

In a nutshell, this is what we’ll be creating:

Table 1

We structure our endpoints in accordance with common RESTful guidelines so that we have clear endpoints that return expected resources.

Setup

First, we use virtualenv and virtualenvwrapper to make a virtual environment to install Python packages in a way that doesn’t interfere with other projects and environments. In this environment, we install Django, Django Rest Framework, and coreapi (for Django Rest Framework)

ktruong:auth-api ktruong$ which python3 /usr/local/bin/python3 ktruong:auth-api ktruong$ mkvirtualenv — python=/usr/local/bin/python3 auth-api … (auth-api) ktruong:auth-api ktruong$ pip install django djangorestframework coreapi Now, we create the django project, make folders, and adjust some project settings: (auth-api) ktruong:auth-api ktruong$ pwd /Users/ktruong/git_projects/blogs/auth-api (auth-api) ktruong:auth-api ktruong$ touch README.md (auth-api) ktruong:auth-api ktruong$ django-admin.py startproject auth_api (auth-api) ktruong:auth-api ktruong$ cd auth_api (auth-api) ktruong:auth_api ktruong$ ls auth_api manage.py (auth-api) ktruong:auth_api ktruong$ python manage.py startapp users (auth-api) ktruong:auth_api ktruong$ python manage.py startapp todos (auth-api) ktruong:auth_api ktruong$ ls auth_api manage.py todos users (auth-api) ktruong:auth_api ktruong$

settings.py

Notice how we explicitly set our AUTH_USER_MODEL to a custom User model (we’ll write the actual model, users.User, later). We could use the default User model that comes from Django but it becomes unnecessarily complicated to change it down the road.

A solution is to write our own User model that subclasses the same AbstractUser model that Django’s User model subclasses. Doing this will give us the same functionality but allow us to easily customize our User model down the road.

Project Level URL Routing

We’ll be building the features in this order:

URLs → views → serializers → models

This means we’ll be referencing some files before we make them, which may feel strange, but I feel doing it this way is more intuitive because it follows the path of the HTTP request more closely.

When the request first comes into our server, we need to decide where to route that request. Kind of like a receptionist to our web server, our top-most URL routes will route the request to the proper modules and views.

We’ll use the innermost auth_api folder to store the top-most url routes along with our project-wide settings:

auth_api/urls.py

When Django parses the incoming request, it will use regex to match the URL to the urlpatterns we write and forward the request to the place we want, which could contain more URL routes or a view.

We set up routes for our Django admin, documentation (automatically generated thanks to DRF), routes for users, routes for todos, and our api root.

Making the Root View of the API

One of the routes we defined earlier routed to views.api_root. Though not necessary, making a root view that acts as a table of contents to other routes in your api when requests matches your domain name exactly is easy to implement and improves developer experience.

The urlpatterns we wrote will route requests, and the views we will write will handle those requests and return HTTP responses. To write our api_root, we can use DRF’s built in decorator, @api_view, to wrap our view with some nice utilities:

Specifies which HTTP methods we allow the view to respond to

Wraps normal HTTP request and response objects to provide a more uniform interface to work with HTTP data.

auth_api/views.py

Configuring URLs for Users and Todos

We need to create routes for when the request matches /todos/* or /users/* (as per the table we made earlier) and route them to the proper views to be handled:

todos/urls.py

users/urls.py

Writing the Views

Views handle the request and return a response, and by handling, I mean we can do anything we want with it — from hitting the database, modifying the request, structuring the response, or injecting our own logic into it.

With respect to APIs, all the core views often do the same thing conceptually:

Parse the request for data and the HTTP method

Query the database (DB) to fetch the model object(s), if needed

Serialize the data (we’ll discuss this more later)

Do something with the object/data (create, read, update, delete)

Return an HTTP response

Views are most often written as functions and an example of a function-based view that handles our API request looks something like this:

Examples from http://www.django-rest-framework.org/tutorial/2-requests-and-responses/

So again, we see that the things that views essentially do are:

Parse the request for data and the HTTP method

Query the database (DB) to fetch the model object(s), if needed

Serialize the data (we’ll discuss this more later)

Do something with the object/data (create, read, update, delete)

Return an HTTP response

It’s relatively straight-forward and explicit, but imagine having to write our views likes this for:

Todo_detail

Todo_list

User_detail

User_list

There would be a lot of logic duplication. Instead, we could use an object-oriented approach and use classes and inheritance to reuse common blocks of logic. Using class-based views, we could write our views like this:

todos/views.py:

With that, we accomplish the exact same functionality as the example above that uses function-based views, only with significantly less code.

But less code is not always better, so you’ll have to decide where to draw the line in terms of how explicit or terse you want your code to be. Personally, I feel class-based views strike that perfect balance, plus they force you to grow as a developer by exposing you to classes, inheritance, and object-oriented programming, which is probably the bulk of the code you’ll be reading and/or writing as a developer.

In the above classes, we subclass generic classes provided by DRF and the generic classes provide methods and properties that encapsulate the common logic so we don’t have to keep rewriting it, but we can still access, overwrite, and customize the logic if needed.

Blocks of logic like the ones that parse the DB for the correct model instance, or carry out error handling, or determining which serializer to use and how to instantiate it, or structure the response, or parsing the request, are already written for you and are readily available to the classes that you write if you subclass the generic ones.

There is honestly a lot of cleverly written code hidden behind these generic views with a lot of concepts and techniques to learn from that are beyond the scope of this guide, but the best way to really learn and understand is to download the source code, read it, and tinker. I also recommend reading the documentation on these topics:

https://docs.djangoproject.com/en/1.11/topics/class-based-views/

http://www.django-rest-framework.org/api-guide/generic-views/

https://github.com/encode/django-rest-framework/tree/master

https://github.com/django/django

Try not to overthink it too much and remember that the 10 or so lines of code using class-based views does the exact same thing as the many more lines of codes using function-based views in the above example, just more concise.

Repeat for Users:

users/views.py

Writing the Serializers

DRF serializers provide the service of serialization and deserialization. Serialization is the process of translating data structures into a format that can be stored, which in this case means turning querysets and model instances into native Python datatypes and then into JSON. Deserialization is the opposite, taking JSON and turning it into native Python datatypes and then into model instances.

Serializers are not magic.

If you’ve made an API in any language then you’ve used the same concepts serializers use. Serializers just give you a convenient interface to take data in one form and convert it into another. DRF serializers use an interface similar to that of Django forms in that you define the fields of the model you wish to serialize/deserialize and when you instantiate the serializer with data it will do the field validation for you.

http://www.django-rest-framework.org/api-guide/serializers/

Let’s write our serializers for Todos and Users:

Todos/serializers.py

users/serializers.py

Notice that UserSerializer is much more complex than TodoSerializer. Serializers, like our class-based views, come in many varieties and can inherit from many generic base classes. Within those base classes are methods and properties we can overwrite to customize what happens at certain hooks, like when a serializer updates or saves data.

we customize UserSerializer because we want to use the hashing feature that comes with Django’s AbstractUser when updating and saving users for security reasons.

Writing the Models

From client to url routes to views to serializers, and now to the last part in our little app, the models.

Our models are what define our Data. Django models get written in normal Python classes and they get mapped to SQL DBs, each attribute on the model getting mapped to a field in its respective table.

https://docs.djangoproject.com/en/1.11/topics/db/models/

We’ll create two models, Todo and User, and we’ll create a many-to-one relationship (foreign key) from our Todos to our Users, so a User can have many Todos but a Todo will only have one User.

todos/models.py

users/models.py

We touched a bit on this earlier, but we don’t do anything extra with our User model. We use the same AbstractUser that the default Django User model uses, but defining our own User model gives us the ability to easily edit it later on while still retaining the same features as the default Django User.

Bringing It All Together

We’ve got all the code we need to make this API work, and now we just need to migrate our models and create a superuser.

ktruong$ python manage.py makemigrations todos users ktruong$ python manage.py migrate ktruong$ python manage.py createsuperuser ktruong$ python manage.py runserver

Now go browse localhost:8000, login to the admin if needed, and play around with your new API.

The browsable API is just another out-of-the-box feature from DRF and it allows you to browse your API interactively in a browser. You can click through links, see relationships, see models and objects, perform any CRUD (create, read, update, delete) request, and do pretty much anything you can do with an API, but in a visible and interactive way.

Conclusion

We covered some fundamental concepts of building a RESTful API by creating a basic Todo API, with complete CRUD endpoints. Though very barebones, the instructions in this guide should serve as another step in the ladder to help you become a better developer and understand how to make your own API.

There’s still a lot more one can do to improve your API, such as adding authentication and authorization with JSON Web Tokens, adding unit tests, and more, which are also topics I plan on covering in the future.