Building GraphQL APIs in Django with Graphene

March 26, 2021
Written by
Adeyemi Atoyegbe
Contributor
Opinions expressed by Twilio contributors are their own

Building GraphQL APIs in Django with Graphene

GraphQL is an open-source data query and manipulation language for APIs, and a runtime for fulfilling queries with existing data. It was developed internally by Facebook in 2012 before being publicly released in 2015. It allows clients to define the structure of the data required, and the same structure of the data is returned from the server, therefore preventing unnecessary data from being returned.

GraphQL has three primary operations: Queries for reading data, Mutations for writing data, and Subscriptions for automatically receiving real-time data updates. A GraphQL server provides clients with a predefined schema – a model of the data that can be requested. The schema serves as common ground between the client and the server.

In this tutorial we will use Graphene, a GraphQL framework for Python, to build a Django API that uses queries and mutations.

Tutorial Requirements

To follow along with this tutorial you should have the following items:

  • Python 3.6 or newer.
  • Virtualenv, to create a virtual environment for the tutorial project.
  • Postman, to send requests to our API.
  • A working knowledge of the Django web framework.

Project setup

We will begin by creating a virtual environment and installing necessary Python packages.

Create a folder for our project:

mkdir django_graphql
cd django_graphql

Then create a Python virtual environment and activate it. If you are following this tutorial on Windows:

$ virtualenv env
$ env\Scripts\activate

If you are using a Mac OS or Unix computer:

$ virtualenv env
$ source env/bin/activate

Install the dependencies required for our project:

(env) $ pip install django graphene-django

Create a new django project:

(env) $ django-admin startproject books_api

Change your current directory to the project:

(env) $ cd books_api

Create a api app in the books_api project

(env) $ python manage.py startapp api

Next register the api app and integrate the graphene-django third-party app we installed earlier into our books_api project. Find the INSTALLED_APPS list In books_api/settings.py and add api and `graphene-django’ at the end:

INSTALLED_APPS = [
    ...
    'api',
    'graphene_django',
]

While in books_api/settings.py, go to the bottom of the file and add a GRAPHENE dictionary with settings for the graphene-django package:

GRAPHENE = {   
    "SCHEMA": "api.schema.schema"
}

The SCHEMA setting tells Graphene where to find the GraphQL schema for the application. We’ll define the schema after we have the database created.

Database model

Open the api/models.py file and type in the code below to add the Book database model:

from django.db import models

class Book(models.Model):
    title = models.CharField(max_length=100)
    author = models.CharField(max_length=50)
    year_published = models.CharField(max_length=10)
    review = models.PositiveIntegerField()
    
    def __str__(self):
        return self.title

Then create and run the migrations for our database:

 $ python manage.py makemigrations
 $ python manage.py migrate

To help in testing this project we can now populate our database with some data. To do this, you create a data.json file in the project directory where the manage.py file is, and copy the following data into it:

    {
        "model": "api.book"

With the data.json file saved to the current directory, run the command below to import the data into the database:

$ python manage.py loaddata data.json
Installed 5 object(s) from 1 fixture(s)

Next, add the GraphQL endpoint at the end of the urlpatterns dictionary in file books_api/urls.py:

from django.contrib import admin
from django.urls import path
from graphene_django.views import GraphQLView

urlpatterns = [
    path('admin/', admin.site.urls),
    path('graphql', GraphQLView.as_view(graphiql=True)),
]

Building a Books API with GraphQL

In this section we will be building an API with Graphene using GraphQL queries and mutations.

Implementing a GraphQL Schema

Create a new file in the api/schema.py folder:

import graphene

from graphene_django import DjangoObjectType, DjangoListField 
from .models import Book 


class BookType(DjangoObjectType): 
    class Meta:
        model = Book
        fields = "__all__"


class Query(graphene.ObjectType):
    all_books = graphene.List(BookType)
    book = graphene.Field(BookType, book_id=graphene.Int())

    def resolve_all_books(self, info, **kwargs):
        return Book.objects.all()

    def resolve_book(self, info, book_id):
        return Book.objects.get(pk=book_id)

In this step we have created two classes, The first one is BookType, which adapts the Book model to a DjangoObjectType. We set fields to __all__ to indicate that we want all the fields in the model available in our API.

The Query class defines the GraphQL queries that the API will provide to clients. The all_books query will return a list of all the BookType instances, while the book query will return one BookType instance, given by an integer ID. The class defines two methods, which are the query “resolvers”. Every query in the schema maps to a resolver method.

The two query resolvers query the database using the Django model to execute the query and return the results.

Adding data updates with GraphQL mutations

We will now add create, update and delete operations through mutations. While still in the api/schema.py file, add the code below at the bottom:

class BookInput(graphene.InputObjectType):
    id = graphene.ID()
    title = graphene.String()
    author = graphene.String()
    year_published = graphene.String()
    review = graphene.Int()

The BookInput class defines fields similar to our Book model object to allow the client to add or change the data through the API. We will use this class as an argument for our mutation classes.

Let’s add a mutation to create new books. Add the following code at the bottom of api/schema.py:

class CreateBook(graphene.Mutation):
    class Arguments:
        book_data = BookInput(required=True)

    book = graphene.Field(BookType)

    @staticmethod
    def mutate(root, info, book_data=None):
        book_instance = Book( 
            title=book_data.title,
            author=book_data.author,
            year_published=book_data.year_published,
            review=book_data.review
        )
        book_instance.save()
        return CreateBook(book=book_instance)

The CreateBook class will be used to create and save new Book entries to the database. For every mutation class we must have an Arguments inner class and a mutate() class method.

We defined an instance of the BookInput class we created earlier as our arguments, and we made it mandatory with the required=True option.  After that we defined the model we are working with by doing this book = graphene.Field(BookType).

In the mutate method we are saving a new book by calling the save() method on a new Book instance created from the book_data values passed as argument.

Below you can see the implementation of the UpdateBook mutation. Add this code at the bottom of api/schema.py:

class UpdateBook(graphene.Mutation):
    class Arguments:
        book_data = BookInput(required=True)

    book = graphene.Field(BookType)

    @staticmethod
    def mutate(root, info, book_data=None):

        book_instance = Book.objects.get(pk=book_data.id)

        if book_instance:
            book_instance.title = book_data.title
            book_instance.author = book_data.author
            book_instance.year_published = book_data.year_published
            book_instance.review = book_data.review
            book_instance.save()

            return UpdateBook(book=book_instance)
        return UpdateBook(book=None)

The UpdateBook mutation class is very similar to CreateBook. The difference here is the logic in the mutate() method, which retrieves a particular book object from the database by the book ID provided and then applies the changes from the input argument to it.

Finally, let’s add a delete mutation. Add the code that follows at the bottom of api/schema.py:

class DeleteBook(graphene.Mutation):
    class Arguments:
        id = graphene.ID()

    book = graphene.Field(BookType)

    @staticmethod
    def mutate(root, info, id):
        book_instance = Book.objects.get(pk=id)
        book_instance.delete()

        return None

In the DeleteBook mutation class we have graphene.ID as the only argument. The mutate() method uses this id to remove the referenced book from the database.

We now have two queries and three mutations defined. To register these with Graphene, add the code below at the end of api/schema.py:

class Mutation(graphene.ObjectType):
    create_book = CreateBook.Field()
    update_book = UpdateBook.Field()
    delete_book = DeleteBook.Field()


schema = graphene.Schema(query=Query, mutation=Mutation)

Testing the GraphQL API

We are not ready to test the API. Let’s start the Django server:

$ python manage.py runserver

Now visit http://127.0.0.1:8000/graphql in your browser. You should see the GraphIQL interface for interactive testing of the GraphQL API.

GraphIQL interface

The black arrow in the diagram above is  where you input your GraphQL code. Then you click on the play button in the top left corner of the screen to run the code and get a result in the area indicated with the blue arrow.

Issuing a query

Queries are used to request data from the server. The GraphQL code below is requesting all the books from the database. Enter it in the left-side panel of the GraphIQL interface.

query {
  allBooks {
    id
    title
    author
    yearPublished
    review
  }
}

Press the play button to execute the query and see the results in the right-side panel.

Next try the following query, which requests a single book by its id:

query {
  book(bookId: 2) {
    id
    title
    author
  }
}

Note how each query can specify which of the attributes of the book model need to be returned.

Creating a book

The following GraphQL snippet defines a mutation that adds a new book to the database:

mutation createMutation {
  createBook(bookData: {title: "Things Apart", author: "Chinua Achebe", yearPublished: "1985", review: 3}) {
    book {
      title,
      author,
      yearPublished,
      review
    }
  }
}

Updating an existing book

The next GraphQL mutation updates the book with id=6:

mutation updateMutation {
  updateBook(bookData: {id: 6, title: "Things Fall Apart", author: "Chinua Achebe", yearPublished: "1958", review: 5}) {
    book {
      title,
      author,
      yearPublished,
      review
    }
  }
}

Deleting a book

The final mutation example deletes the book with id=6 from the database:

mutation deleteMutation{
  deleteBook(id: 6) {
    book {
      id
    } 
  }
}

Testing the Book API with other GraphQL clients

Django CSRF prevents unauthenticated users on the website from performing malicious attacks. Given this, any POST  request made from an external application outside the Django site will result in a 403 Forbidden Error.

To avoid this there are two options. The most secure option is to add the CSRF token generated by the Django application to the POST requests that your client makes to the GraphQL endpoint. See the Ajax section in the Django documentation to learn about this option.

An easier, but less secure option is to exempt the GraphQL endpoint from CSRF protection. To do that, open theapi/urls.py file and change the definition of the GraphQL endpoint as follows:

from django.urls import path 
from graphene_django.views import GraphQLView 
from django.views.decorators.csrf import csrf_exempt

urlpatterns = [
    ...
    path('graphql', csrf_exempt(GraphQLView.as_view(graphiql=True))),
]

The csrf_exempt wrapper added to the GraphQLView removes the CSRF token verification from the endpoint.

If you want to make sure the CSRF protection does not interfere with your GraphQL endpoint, you can use Postman to send GraphQL requests to the Django API:

 

Postman interface

Using the above screenshot as a reference, follow these steps to send a GraphQL request with Postman:

  • Paste your GraphQL endpoint http://127.0.0.1:8000/graphql in the box with the purple arrow.
  • Click on the first white arrow pointing to the “Body” option
  • Click on the GraphQL options at the second white arrow
  • Paste your query code in the query box and click on the “Send” blue button.
  • You will see the result of your API request at the bottom, in the light green arrow area.
  • Notice the blue arrow area, where you should be making a GET request for queries, or a POST request for mutations.

Try the code snippets we used above to test our API through Postman.

Conclusion

In this tutorial we have created a simple GraphQL API in Django using the Graphene-Django package built on top of Graphene, which made it easy to add GraphQL functionality to our Django application.

We created queries to read data, mutations to write and change data and tested our API using the GraphIQL interface provided by the Graphene-Django library and the popular API client Postman. You can find the complete code for the project here.

Adeyemi Atoyegbe is a self-taught Python developer. You can find him on Twitter, GitHub.