Working With GraphQL and Django

If you've ever wondered if there is a better way to build and consume APIs, you should check out GraphQL—it's better than REST in many cases. This article discusses how to work with GraphQL in Django.

Have you ever wondered whether there is a better way to develop APIs other than REST? If you have asked this question, then you are in luck. GraphQL is an open-source runtime for querying existing data, as well as a manipulation language for APIs.

In this article, you will learn what GraphQL is about, the problem it solves, and how to go about connecting it with Django to build amazing APIs. You will learn different ways to use GraphQL to query a database and set up authentication when you are building an API. Building a GraphQL API with Django requires some configurations, and I will explain all of them as we go.

You can get the full project on GitHub.

Prerequisites

To follow this article, the following are required:

  • Prior knowledge of Django and Python
  • Python v3.1
  • Django v3.8

A Brief Introduction to GraphQL and How it Works

As I said earlier, GraphQL is a data query and manipulation language for APIs. GraphQL was developed to solve a problem developers were having with REST that led to the over-fetching or under-fetching of data, as well as inflexibility with impromptu API changes.

GraphQL enables you to make declarative data requests, which means that you can describe the data you want, and GraphQL provides it specifically. It exposes a single endpoint and responds with precise data from the client's request.

To get data from a GraphQL API, create queries from the frontend or client to get data from that particular endpoint.

Setting up GraphQL to Work with Django

To build GraphQL APIs with Django, there are some settings you will have to go through, and in this section, I will go through how you can do that.

Assuming you have Python and Django installed, follow the steps below to set up GraphQL:

  1. Install Graphene and Graphene-Django. Graphene is a tool that makes working with GraphQL in Python easy, while Graphene-Django adds some additional abstractions to make adding GraphQL functionality to your Django project a breeze. To install Graphene and Graphene-Django, run the following on your command line/terminal, pip install graphene graphene-django.
  2. Create a new directory for your project, navigate to the directory you just created and create a new Django project and app by running django-admin startproject graphql_tutorial . && python3 manage.py startapp app.
  3. Add 'app' to INSTALLED_APPS in your settings.py file.

Ways to Query the Database When working With GraphQL

In this section, you will learn how to make some basic queries with GraphQL in Django.

Open your preferred text editor, navigate to app/models.py, and paste the code below, which is just a simple model for a contact list application.

from django.db import models

class Contact(models.Model):
    name = models.CharField(max_length=200)
    phone_number=models.CharField(max_length=200)
    def __str__(self):
        return self.name

Next, we must create a schema for the model. In this case, the schema describes the models that will be provided to GraphQL; you can also think of it as serializers in REST.

To set up the schema, create a new file at the same folder where you have your settings.py file and name it schema.py; then, paste the following code:

import graphene
from graphene_django import DjangoObjectType #used to change Django object into a format that is readable by GraphQL
from app.models import Contact

class ContactType(DjangoObjectType):
    # Describe the data that is to be formatted into GraphQL fields
    class Meta:
        model = Contact
        field = ("id", "name", "phone_number")

class Query(graphene.ObjectType):
    #query ContactType to get list of contacts
    list_contact=graphene.List(ContactType)

    def resolve_list_contact(root, info):
        # We can easily optimize query count in the resolve method
        return Contact.objects.all()

schema = graphene.Schema(query=Query)

Next, paste the following code sample at the bottom part of your settings.py file to state the schema location for Graphene.

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

Then, to use Graphene-Django in your Django project, add it to INSTALLED_APPS in your setting.py file.

INSTALLED_APPS = [
# ...
'app',
'graphene_django',
]

Now, replace the urls.py file of your project with the code sample below:

from django.contrib import admin
from django.urls import path
from django.views.decorators.csrf import csrf_exempt
from graphene_django.views import GraphQLView #View for the user interface
from graphql_tutorial.schema import schema #Schema we want to query

urlpatterns = [
    path('admin/', admin.site.urls),
    # This URL will provide a user interface that is used to query the database
    # and interact with the GraphQL API.
    path("graphql", csrf_exempt(GraphQLView.as_view(graphiql=True, schema=schema))),
]

Next, migrate the database to apply the model objects in the database. You can do this by running the following commands:

python manage.py makemigrations
python manage.py migrate 

After the migrations are done, we need to create a superuser that you will use to add some data to the database. You can create a superuser by running the command below and filling up the required information.

python manage.py createsuperuser

Now we have to make our models visible in the admin page so that we can add some data to the database. To do this, navigate to app/admin.py and paste the following code:

from django.contrib import admin
from .models import Contact
admin.site.register(Contact)

Now go to http://127.0.0.1:8000/admin on your browser and log in with the user you just created and populate the database.

Next, open http://127.0.0.1:8000/graphql and press the run button to query the API and get the data from the database.

List of contacts

Note: Graphene changes fields with underscore camelCase; it change phone_number to phoneNumber.

Build GraphQL API CRUD Operations

In the previous section, we went through how to set up GraphQL to work with Django and fetch some data from the database.

In this section, you will learn how to add create, read, update, and delete functionalities in your GraphQL API. You will learn how to code out functionalities to create, read, update, and delete a particular contact.

Read

Next, we will create an endpoint where the frontend will be able to query the API with a particular id, and the front end will display the contact attached to that id.

To do this, go to your schema.py file and update the Query class with the following code:

# ...
class Query(graphene.ObjectType):
    #query ContactType to get list of contacts
    list_contact=graphene.List(ContactType)
    read_contact = graphene.Field(ContactType, id=graphene.Int()) # id=graphene.Int() gives id an integer datatype

    def resolve_list_contact(root, info):
        # We can easily optimize query count in the resolve method
        return Contact.objects.all()
    def resolve_read_contact(root, info, id):
        # get data where id in the database = id queried from the frontend
        return Contact.objects.get(id=id)

schema = graphene.Schema(query=Query)

Now run your server, open the GraphQL UI, on your browser and run the following:

query {
  readContact(id: 1) {
    name
    phoneNumber
  }
}

You should then see the contact attached to id=1.

View contact with ID

Create

To create new data in the database, we need a class called Mutation instead of Query, which we used previously. Mutation in GraphQL is used to modify data or create data in the database and returns a value.

Add create functionality by pasting the code below into your schema.py file. You can put it right below the Query class.

class ContactMutation(graphene.Mutation):
    class Arguments:
        # Add fields you would like to create. This will corelate with the ContactType fields above.
        name=graphene.String()
        phone_number=graphene.String()
    contact = graphene.Field(ContactType) # define the class we are getting the fields from
    @classmethod
    def mutate(cls, root, info, name, phone_number):
        # function that will save the data
        contact = Contact(name=name, phone_number=phone_number) #accepts all fields
        contact.save() #d=save the contact

class Mutation(graphene.ObjectType):
    # keywords that will be used to do the mutation in the frontend
    create_contact= ContactMutation.Field()     

schema = graphene.Schema(query=Query, mutation=Mutation) # Tell the schema about the mutation you just created.

Now run your server, open the GraphQL UI on your browser, and run the following:

mutation nameOfMutation {
  createContact(name: "New Contact", phoneNumber: "486054-850"){
    contact {
      name,
      phoneNumber
    }
  }
}

Next, still on the browser, run the following command, and you will see that the contact you just saved is included in the list of contacts.

{
  listContact {
      id,
      name,
      phoneNumber
    }
  }

check list of contacts

Update

To update, we will get a particular contact using their id and then use GraphQL to update the data. You can do this by updating your ContactMutation and Mutation class with the code below:

class ContactMutation(graphene.Mutation):
    class Arguments:
        # add fields you will like to create. This will corelate with the ContactType fields above
        id = graphene.ID() # new
        name=graphene.String()
        phone_number=graphene.String()
    contact = graphene.Field(ContactType) # define the class we are getting the fields from

    @classmethod
    def mutate(cls, root, info, name, phone_number, id):
        # function that will save the data
       ###########Create##############
        contact = Contact(name=name, phone_number=phone_number) #accepts all fields
        contact.save() #save the contact

      ###########Update##############
        get_contact = Contact.objects.get(id=id)
        get_contact.name = name #override name
        get_contact.phone_number = phone_number #override phone_number
        get_contact.save()
        return ContactMutation(contact=get_contact)

class Mutation(graphene.ObjectType):
    # keywords that will be used to do the mutation in the frontend
    create_contact = ContactMutation.Field()  
    update_contact = ContactMutation.Field() #new

Now if you run the query below on the browser, you will see that the contact has been updated successfully.

mutation update {
  updateContact(id: 1, name: "Updated name", phoneNumber: "364839237") {
    contact{
      name
      phoneNumber
     }
  }
}

Update phone number with id

Delete

To delete, you need to get the data related to a particular id and then use the delete() function to delete it.

You can add delete functionality by adding the new class below to your schema.py file.

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

    contact = graphene.Field(ContactType)

    @classmethod   
    def mutate(cls, root, info, id):
        contact = Contact(id=id) 
        #########Delete##############
        contact.delete()

Then add delete_contact = ContactDelete.Field() to your Mutation class. Now if you run the query below on your browser, you will see that GraphQL deleted the contact with id of 1.

mutation delete {
  deleteContact(id: 1) {
   contact {
        id
      }
    }
  }

Setting up Authentication When Working on GraphQL API in Django

In this section, you will learn how to build a GraphQL authentication backend with Django.

To start, create a new app with python manage.py startapp users and then add "users" to INSTALLED_APPS in your settings.py file.

Then, paste the code below into your users/models.py file to create a model that uses email for authentication.

from django.db import models
from django.contrib.auth.models import AbstractUser

class User(AbstractUser):
    email = models.EmailField(blank=False, verbose_name="Email")

    EMAIL_FIELD = "email"
    USERNAME_FIELD = "username"

Next, go to your settings.py file and add AUTH_USER_MODEL = 'users.User' to tell Django to use your model for users authentication instead of the default.

Run the commands below to apply your model to the database.

python manage.py makemigrations
python manage.py migrate

Set up GraphQL with JWT in Django

JSON Web Token (JWT) is a standard that specifies a concise and self-contained method for securely exchanging information as a JSON object between parties. In this case, we will be using it for authentication. Here, each request will include the JWT once the user has authenticated, allowing the user to access routes, resources permitted with that token.

Luckily for us, we have an already created a tool that enables us to use JWT in GraphQL API in Django. The tool we will be working with is django-graphql-jwt.

First, install the tool with pip install django-graphql-jwt.

Add "graphql_jwt.refresh_token.apps.RefreshTokenConfig" to your INSTALLED_APPS to tell Django to generate new access tokens after some time.

Next, add AuthenticationMiddleware middleware to MIDDLEWARE in your settings.py file:

MIDDLEWARE = [
     # ...
    "django.contrib.auth.middleware.AuthenticationMiddleware",
     # ...
]

Add JSONWebTokenMiddleware middleware to GRAPHENE in your settings.py file:

GRAPHENE = {
    # ...
    "MIDDLEWARE": [
        "graphql_jwt.middleware.JSONWebTokenMiddleware",
    ],
}

Set up Authentication for Django and GraphQL

In this section, we will set up authentication in Django and GraphQL API. You will need another tool called Django GraphQL Auth. Install Django GraphQL Auth by running pip install django-graphql-auth.

Next, add "graphql_auth", "django_filters" to your INSTALLED_APPS; 'django_filters' is required by graphql_auth.

Now, paste the code below into your settings.py file to tell Django about the new authentication settings.

AUTHENTICATION_BACKENDS = [
    "graphql_auth.backends.GraphQLAuthBackend",
    "django.contrib.auth.backends.ModelBackend",
]

Then, run python manage.py migrate to apply refresh_token and graphql_auth models and paste the following code into your settings.py file:

GRAPHQL_JWT = {
    "JWT_ALLOW_ANY_CLASSES": [
        #connect GraphQL Auth to GraphQL JWT for authentication
        "graphql_auth.mutations.Register",
        "graphql_auth.mutations.VerifyAccount",
        "graphql_auth.mutations.ObtainJSONWebToken",# get jwt to log in
    ],
    "JWT_VERIFY_EXPIRATION": True, # affirm that the jwt token will expire
    "JWT_LONG_RUNNING_REFRESH_TOKEN": True,
}

EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' #used to receive email in the backend to verify an account. This settings can be changed to the email provider you prefer

Then, add the class below (AuthMutation) to your schema.py file to handle the registration, verification, and logging in of users.

# ...
from graphql_auth.schema import UserQuery
from graphql_auth import mutations
# ...
class Query(UserQuery, graphene.ObjectType):# update
# ...
class AuthMutation(graphene.ObjectType):
   register = mutations.Register.Field() #predefined settings to register user
   verify_account = mutations.VerifyAccount.Field() #used to verify account
   token_auth = mutations.ObtainJSONWebToken.Field() # get jwt to log in

class Mutation(AuthMutation, graphene.ObjectType): # update
# ...

Now, run your server and open the Graphql endpoint on the browser and run the following JSON to register a new user:

mutation {
  register (
    email: "admin2@gmail.com",
    username: "admin2",
    password1: "djh83rg49390r",
    password2: "djh83rg49390r"
  ) {
    success,
    errors,
    token,
    refreshToken,
  }
}

Image for user registration

At the moment, the user you just created is not verified. To verify the user, copy the verification token from the email sent to the terminal when you created the new user.

display Verification token

After copying the token, run the command below with that token.

mutation {
  verifyAccount(token: "eyJ1c2VybmFtZSI6ImFkbWluMiIsImFjdGlvbiI6ImFjdGl2YXRpb24ifQ:1nJCGK:MTsAZVqLtdNGY4hxkxN5KNoRhEXDjjL8JFSg59yhkaA") {
    success
    errors
  }
}

You can now log in by running the query below:

mutation {
  tokenAuth(username: "admin2", password: "djh83rg49390r") {
    success,
    errors
  }
}

logging in of user

Conclusion

In this article, you have learned what GraphQL is about, the problem it solves, and how to go about connecting it with Django to build APIs. We also learned a bit about authentication and the different ways in which you can use GraphQL to query, update, and delete from the database when you are building an API.

How will you use your newly gained knowledge? Perhaps you will add the logic to provide each user with personalized contacts?

What to do next:
  1. Try Honeybadger for FREE
    Honeybadger helps you find and fix errors before your users can even report them. Get set up in minutes and check monitoring off your to-do list.
    Start free trial
    Easy 5-minute setup — No credit card required
  2. Get the Honeybadger newsletter
    Each month we share news, best practices, and stories from the DevOps & monitoring community—exclusively for developers like you.
    author photo

    Muhammed Ali

    Muhammed is a Software Developer with a passion for technical writing and open source contribution. His areas of expertise are full-stack web development and DevOps.

    More articles by Muhammed Ali
    Stop wasting time manually checking logs for errors!

    Try the only application health monitoring tool that allows you to track application errors, uptime, and cron jobs in one simple platform.

    • Know when critical errors occur, and which customers are affected.
    • Respond instantly when your systems go down.
    • Improve the health of your systems over time.
    • Fix problems before your customers can report them!

    As developers ourselves, we hated wasting time tracking down errors—so we built the system we always wanted.

    Honeybadger tracks everything you need and nothing you don't, creating one simple solution to keep your application running and error free so you can do what you do best—release new code. Try it free and see for yourself.

    Start free trial
    Simple 5-minute setup — No credit card required

    Learn more

    "We've looked at a lot of error management systems. Honeybadger is head and shoulders above the rest and somehow gets better with every new release."
    — Michael Smith, Cofounder & CTO of YvesBlue

    Honeybadger is trusted by top companies like:

    “Everyone is in love with Honeybadger ... the UI is spot on.”
    Molly Struve, Sr. Site Reliability Engineer, Netflix
    Start free trial
    Are you using Sentry, Rollbar, Bugsnag, or Airbrake for your monitoring? Honeybadger includes error tracking with a whole suite of amazing monitoring tools — all for probably less than you're paying now. Discover why so many companies are switching to Honeybadger here.
    Start free trial
    Stop digging through chat logs to find the bug-fix someone mentioned last month. Honeybadger's built-in issue tracker keeps discussion central to each error, so that if it pops up again you'll be able to pick up right where you left off.
    Start free trial
    “Wow — Customers are blown away that I email them so quickly after an error.”
    Chris Patton, Founder of Punchpass.com
    Start free trial