Building a GraphQL API with Django

Introduction

Web APIs are the engines that power most of our applications today. For many years REST has been the dominant architecture for APIs, but in this article we will explore GraphQL.

With REST APIs, you generally create URLs for every object of data that's accessible. Let's say we're building a REST API for movies - we'll have URLs for the movies themselves, actors, awards, directors, producers... it's already getting unwieldy! This could mean a lot of requests for one batch of related data. Imagine you were the user of a low powered mobile phone over a slow internet connection, this situation isn't ideal.

GraphQL is not an API architecture like REST, it's a language that allows us share related data in a much easier fashion. We'll use it to design an API for movies. Afterwards, we'll look at how the Graphene library enables us to build APIs in Python by making a movie API with Django.

What is GraphQL

Originally created by Facebook but now developed under the GraphQL Foundation, GraphQL is a query language and server runtime that allows us to retrieve and manipulate data.

We leverage GraphQL's strongly-typed system to define the data we want available to the API. We then create a schema for the API - the set of allowed queries to retrieve and alter data.

Designing a Movie Schema

Creating Our Types

Types describe the kind of data that's available in the API. There are already provided primitive types that we can use, but we can also define our own custom types.

The createActor mutator needs an ActorInput object, which requires the name of the actor.

The updateActor mutator requires the ID of the actor being updated as well as the updated information.

The same follows for the movie mutators.

Note: While the ActorPayload and MoviePayload are not necessary for a successful mutation, it's good practice for APIs to provide feedback when it processes an action.

Defining the Schema

Finally, we map the queries and mutations we've created to the schema:

schema {
query: Query
mutation: Mutation
}

Using the Graphene Library

GraphQL is platform agnostic, one can create a GraphQL server with a variety of programming languages (Java, PHP, Go), frameworks (Node.js, Symfony, Rails) or platforms like Apollo.

With Graphene, we do not have to use GraphQL's syntax to create a schema, we only use Python! This open source library has also been integrated with Django so that we can create schemas by referencing our application's models.

Application Setup

Virtual Environments

It's considered best practice to create virtual environments for Django projects. Since Python 3.6, the venv module has been included to create and manage virtual environments.

Using the terminal, enter your workspace and create the following folder:

$ mkdir django_graphql_movies
$ cd django_graphql_movies/

Now create the virtual environment:

$ python3 -m venv env

You should see a new env folder in your directory. We need to activate our virtual environment, so that when we install Python packages they would only be available for this project and not the entire system:

$ ./env/bin/activate

Note: To leave the virtual environment and use your regular shell, type deactivate. You should do this at the end of the tutorial.

Installing and Configuring Django and Graphene

While in our virtual environment, we use pip to install Django and the Graphene library:

$ pip install Django
$ pip install graphene_django

Then we create our Django project:

$ django-admin.py startproject django_graphql_movies .

A Django project can consist of many apps. Apps are reusable components within a project, and it is best practice to create our project with them. Let's create an app for our movies:

$ cd django_graphql_movies/
$ django-admin.py startapp movies

Before we work on our application or run it, we'll sync our databases:

# First return to the project's directory
$ cd ..
# And then run the migrate command
$ python manage.py migrate

Creating a Model

Django models describe the layout of our project's database. Each model is a Python class that's usually mapped to a database table. The class properties are mapped to the database's columns.

As with the GraphQL schema, the Actor model has a name whereas the Movie model has a title, a many-to-many relationship with the actors and a year. The IDs are automatically generated for us by Django.

We can now register our movies app within the project. Go the django_graphql_movies/settings.py and change the INSTALLED_APPS to the following:

The actor and movie properties return one value of ActorType and MovieType respectively, and both require an ID that's an integer.

The actors and movies properties return a list of their respective types.

The four methods we created in the Query class are called resolvers. Resolvers connect the queries in the schema to actual actions done by the database. As is standard in Django, we interact with our database via models.

Consider the resolve_actor function. We retrieve the ID from the query parameters and return the actor from our database with that ID as its primary key. The resolve_actors function simply gets all the actors in the database and returns them as a list.

Making Mutations

When we designed the schema we first created special input types for our mutations. Let's do the same with Graphene, add this to schema.py:

As movies reference actors, we have to retrieve the actor data from the database before saving. The for loop first verifies that the actors provided by the user are indeed in the database, if not it returns without saving any data.

When working with many-to-many relationships in Django, we can only save related data after our object is saved.

That's why we save our movie with movie_instance.save() before setting the actors to it with movie_instance.actors.set(actors).

Testing Our API

To test our API, let's run the project and then go to the GraphQL endpoint. In the terminal type:

$ python manage.py runserver

Once your server is running head to http://127.0.0.1:8000/graphql/. You'll encounter GraphiQL - a built in IDE to run your queries!

Writing Queries

For our first query, let's get all actors in our database. In the top-left pane enter the following:

query getActors {
actors {
id
name
}
}

This is the format for a query in GraphQL. We begin with the query keyword, followed by an optional name for the query. It's good practice to give queries a name as it helps with logging and debugging. GraphQL allows us to specify the fields we want as well - we chose id and name.

Even though we only have one movie in our test data, let's try the movie query and discover another great feature of GraphQL:

query getMovie {
movie(id: 1) {
id
title
actors {
id
name
}
}
}

The movie query requires an ID, so we provide one in brackets. The interesting bit comes with the actors field. In our Django model we included the actors property in our Movie class and specified a many-to-many relationship between them. This allows us to retrieve all the properties of an Actor type that's related to the movie data.

This graph-like traversal of data is a major reason why GraphQL is considered to be powerful and exciting technology!

Writing Mutations

Mutations follow a similar style as queries. Let's add an actor to our database:

An application communicating with your API would send POST requests to the /graphql endpoint. Before we can make POST requests from outside the Django site, we need to change django_graphql_movies/urls.py: