Simple Nested API Using Django REST Framework

In this article you will learn how to build a REST API using Django REST Framework. The code in this article was written with Python 3.6, Django 1.11 and DRF 3.6 in mind.

Two of my wizard-friends found it difficult to create an API using Django REST Framework. Several curses had been cast before they turned to me for help. I decided to write a helpful spellbook of arcane incantations to summon a proper Django REST Framework (referred to as DRF) API. What follows is the first part of said grimoire translated to common speech.

Prerequisites

Python 3.6, Django 1.11 and Django Rest Framework 3.6 were used to construct spells contained here. It is also assumed that every command is run inside a virtualenv. If you’re not familiar with it, no problem, just use sudo pip instead of pip. If you don’t have Python 3.6 yet, you shall port (or remove) __str__ methods as they use new formatted string literals.

Why you should build a REST API using Django REST Framework?

Let’s consider a popular “library” approach. Flat API would have e.g. books and authors endpoints. Searching for books by particular author could then look like this: books/?author={author_id} and that’s quite OK.

But many wizards find it much more logical to lay a nested structure to their API. The same query would then look like this: authors/{author_id}/books and so on. Many frontend tools support such layout automatically hence the need to construct such layout using DRF.

Serializers

OK, time to start brewing our API.

First we need to create some serializers to handle our data interchange (DRF uses JSON by default but you can change that to XML or YAML). Some people argue that this could be made automatically on the ViewSet level. Little do they know that making an API is much like creating a Form-View combo but on a different level. And hardly anyone complains about the Forms ;).

You’ll probably admit it wasn’t all that hard. Probably the worst part is that fields meta attribute is compulsory. You can use the magic value ‘__all__’ but listing specific fields is recommended. It makes your API much safer.

Viewsets and routing

Now it’s time to write the basic viewsets. Note: Usually when I write api-only backend, I use views.py for API views. If you need to separate API views from “normal” web views, you can put this code into e.g. api.py – just remember to update imports in other files accordingly.

The NestedDefaultRouter we made allows us to create subrouters to register nested endpoints. It’s almost as simple as registering normal routers. We only need to add two extra params so that the automation will know how to connect everything together.

base_name needs to be unique across your API. It’s the name that will be the root for url names used by reverse() function.

parents_query_lookups is a list of relations linking to parent models. These values are used as param names for filter() function. In our example this would be author on Book model: queryset = Book.objects.filter(author={value from url})

After these changes we can get a list of all books by a certain author. Reload your server and go to this url: http://127.0.0.1:8000/api/authors/2/books/

Further nesting – here be dragons

The deeper you go with the nesting, the messier will parents_query_lookups get. Let’s add the Edition to the Book to illustrate the problem.

Notice how we chained another register() right after the first one. Note that if you have more endpoints to add on that level, you should instead do the same trick we did with authors_router. Please also notice how parents_query_lookups looks now. The Editions will be found using this filter: