Django: When REST May Not Be Enough and How a GraphQL Layer Can Help Save You

In this article, we’ll present a common problem faced by maintainers of a mature REST-based API which has been built using the Django REST Framework. We’ll then proceed to demonstrate how GraphQL can provide a solution to this problem and how this solution can be applied by adding just two lines of code to your Django project via use of the GraphWrap library.

The Set-Up

Following the typical REST paradigm, an API consumer can find details of all author’s they’re authorised to view via a “list” /author/ endpoint and can view details of a specific author via a “detail” /author/id/ endpoint. Similar endpoints are exposed for book . Under the hood, these endpoints are served by the following DRF serialisers:

class AuthorSerializer(serializers.ModelSerializer):
class Meta:
model = Author
fields = ['name', 'active', 'profile']
class BookSerializer(serializers.ModelSerializer):
author = serializers.HyperlinkedRelatedField(
view_name='author-detail', read_only=True)
class Meta:
model = Book
fields = ['author', 'title', 'page_count']

The Problem

{
'author': '/author/1/',
'title': 'Lovely Gardens',
'page_count': 200,
}

As we can see, the web client doesn’t get much information about the books author here. To get that data, they need to make a second call to /author/1/ . This is the well-known n+1 problem. It can lead to an inefficient website and overly complex front-end architecture; it’s something we want to avoid! So how can we fix this problem?

Attempting a Solution in Native DRF

class BookSerializer(serializers.ModelSerializer):
author = serializers.HyperlinkedRelatedField(
view_name='author-detail', read_only=True)
author_full = AuthorSerializer(source='author_full')
class Meta:
model = Book
fields = ['author', 'author_full', 'title', 'page_count']

Now when a our web client requests /book/1/ , they get a full representation of the author as well:

{
'author': '/author/1/',
'title': 'Lovely Gardens',
'page_count': 200,
'author_full': {
'name': 'Emilie B.',
'active': True,
'profile': '/profile/1/',
}
}

Our web clients can now serve the “book detail” page using a single GET request. Everyone’s happy!

The next day, our app client comes to us and complains that the call to /book/1/ has become slower and is returning information unnecessary for the app. Now what?

In attempting to satisfy one of our API clients, we have inadvertently introduced a whole new class of problems for our other clients. Not only that, but by exposing author fields on the /book endpoint, we’re introducing unnecessary coupling in our API architecture, and we all know what kind of issues that can lead to.

It’s starting to feel like that unless we start building an API per client (which of course we do not want), we’re a bit stuck.

How GraphQL Can Help Us

Applying a GraphQL Layer via GraphWrap

The API development team at P. Walnuts & Co. decide to add GraphWrap into their project. After installing via

pip install graph_wrap

the team expose the new /graphql endpoint on their API by adding the the graphql_view to their urlpatterns :

from rest_framework import routers

from graph_wrap.django_rest_framework.graphql_view import graphql_view


urlpatterns = [
...,
path(r'/graphql/', view=graphql_view),
]

With this new /graphqlendpoint in place, we can now stop overexposing the author fields and instead simply expose author as a URL as we did originally:

class BookSerializer(serializers.ModelSerializer):
author = serializers.HyperlinkedRelatedField(
view_name='author-detail', read_only=True)
class Meta:
model = Book
fields = ['author', 'title', 'page_count']

This keeps our app client, who don’t care about the nested author fields happy. Our web client, who is interested in retrieving the nested author fields, can then do so via a query to the new /graphql endpoint:

query {
book(id: 1) {
title
author {
name
active
}
}
}

This will give a response which looks something like this:

'data': {
'title': 'Lovely Gardens',
'author': {
'name': 'Emilie B.',
'active': True,
} }

This /graphql endpoint allows each client to decide for themselves what information they want from the book endpoint. Both our web client and our app client are now happy!

The most attractive feature of the new /graphql end point for the back-end team (apart from keeping the front-end happy of course!) is that it requires very little extra maintenance due to the following features of GraphWrap:

  • The dynamic nature of the build of the GraphQL layer means that you can continue to develop your existing REST based API and know that the GraphQL schema will be kept up-to-date automatically.
  • Since the GraphQL layer is using the REST API under-the-hood, you can be sure that important things like serialization, authentication, authorization and filtering will be consistent between your REST view and the corresponding GraphQL type.

For more on GraphWrap, see https://github.com/PaulGilmartin/graph_wrap

Python Software Developer based in Copenhagen, Denmark