Working With GraphQL and Django

honeybadger_staff

Honeybadger Staff

Posted on April 18, 2023

Working With GraphQL and Django

This article was originally written by Muhammed Ali on the Honeybadger Developer Blog.

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
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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"
}
Enter fullscreen mode Exit fullscreen mode

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

INSTALLED_APPS = [
# ...
'app',
'graphene_django',
]
Enter fullscreen mode Exit fullscreen mode

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))),
]
Enter fullscreen mode Exit fullscreen mode

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 
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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

query {
  readContact(id: 1) {
    name
    phoneNumber
  }
}
Enter fullscreen mode Exit fullscreen mode

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.
Enter fullscreen mode Exit fullscreen mode

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
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

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
    }
  }
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
     }
  }
}
Enter fullscreen mode Exit fullscreen mode

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()
Enter fullscreen mode Exit fullscreen mode

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
      }
    }
  }
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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",
     # ...
]

Enter fullscreen mode Exit fullscreen mode

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

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

Enter fullscreen mode Exit fullscreen mode

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",
]

Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
# ...
Enter fullscreen mode Exit fullscreen mode

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,
  }
}
Enter fullscreen mode Exit fullscreen mode

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
  }
}
Enter fullscreen mode Exit fullscreen mode

You can now log in by running the query below:

mutation {
  tokenAuth(username: "admin2", password: "djh83rg49390r") {
    success,
    errors
  }
}
Enter fullscreen mode Exit fullscreen mode

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?

💖 💪 🙅 🚩
honeybadger_staff
Honeybadger Staff

Posted on April 18, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related