Contents

    Guides

    Step-by-step guide to use GraphQL with Django

    Vivek Kumar Singh

    Vivek is a Django developer. He spends his time studying new technologies and creating projects. He likes to explain complex concepts in his blogs.

    Published on

    May 27, 2022
    Step-by-step guide to use GraphQL with Django

    Everyone is talking about GraphQL these days. In this post, you'll discover how to use GraphQL with Django, as well as its benefits and other details. 


    We will assume that you have a basic understanding of Django, URL Endpoints, REST, and APIs before continuing with this article.

    What is GraphQL?

    As the official documentation of graphql describes it:

    GraphQL is a query language for your API.

    Graphql is just that, it is a language which is used to request or modify data from an API. Graphql was developed by Facebook and was once a proprietary technology that could only be used by Facebook applications. However, in 2015, Facebook open sourced Graphql, allowing anybody from react developers to Django developers to utilize it. Following the open-source release of GraphQL, dozens of new applications began to use it. GraphQL is currently used by Pinterest, Shopify, and Coursera, to name a few.

    What problem does GraphQL solve?

    There are always issues with using a rest API, such as overfetching, underfetching, and managing several endpoints. GraphQL overcomes this problem by allowing for more flexibility in API querying.

    To give you an example, let's say we're constructing a blog application where a user's post title needs to be displayed on the page. Using REST to achieve this task will almost certainly result in overfetching because we only need the post title but have to retrieve the entire post data. This is where GraphQL comes in; it allows us to use a single endpoint to obtain the exact data we require from the server.

    Setting up the project

    To begin using GraphQL, we must first set up our Django project; I will not go over the standard processes for doing so. We'll make a blog app backend and integrate it with GraphQL for the purposes of this article. You can clone this repository if you want the starter template; otherwise, you can always start from scratch.

    After cloning the repository, be sure to follow the given set of instructions given in the Starter Template README.md

    GraphQL with Django

    To work with GraphQL in Python we need to install graphene which is built on top of GraphQL which provides necessary tools to work smoothly with GraphQL in Python.

    To install graphene for Django, run the following command in your terminal:

    pip install graphene-django==2.13.0

    After successfully installing Graphene for Django we have to add this app in out INSTALLED_APPS setting in our settings.py file.

    # core/settings.py
     
    INSTALLED_APPS = [
        'django.contrib.admin',
        . . .
        . . .
        'django.contrib.staticfiles',
        # Local apps
        'blog',
     
        # 3rd Party apps
        'graphene_django',
    ]

    Now we have set the stage to work with GraphQL, now we need two things:

    • Schema
    • Query Resolver

    Let's take a moment to grasp what our schema and resolver are before we start coding:

    Schema

    In GraphQL, a schema outlines the data that the backend can return and is queried. It is in charge of defining the operation type and the data that can be returned. The client's query gets validated against the schema.

    Query Resolver

    The resolver is responsible for returning the required data once the client's query is validated. The schema specifies what data can be returned, but the resolver specifies how it should be done.

    To grasp the distinction between the two, consider the following. Assume you're creating a blog API. The schema will describe the fields that can be returned, such as title, published_date, and content. Query Resolver is what resolves our queries and retrieves the data we described in our schema(for example: reading it from a database).

    Add the following line in your settings.py file to make your schema available.

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

    Let's create our schema now that we've established its path.

    Creating the Schema

    In the blog directory, create a new file named schema.py, where we will define the schema and resolvers.

    Now add the following code to this file.

    import graphene
    from graphene_django import DjangoObjectType
    from .models import Post
     
    class PostType(DjangoObjectType):
        class Meta:
            model = Post
            fields = ('id', 'title','content')
     
     
    class Query(graphene.ObjectType):
        posts = graphene.List(PostType)
     
        def resolve_posts(self,info):
            return Post.objects.all()
     
    schema = graphene.Schema(query=Query)

    Let's look at the code above to see what's going on.

    First we Created PostType, this is like a serializer that makes our model fields available to query by the client, the above class is extending from DjangoObjectType which is a helper class that helps to map our Django Model to GraphQL Type. You can read more about Types in GraphQL here.

    The previous code produced the Graph of our Django Models, we still need to specify our resolvers to inform graphene how to resolve the queries. To do that, we must construct a Query Class that holds our queries and how to resolve them.

    It's worth noting that each query (q_name) must be accompanied by a resolve function (resolver_q_name); the resolve function requires two positional parameters, so don't overlook them.

    We mentioned in the preceding code that anytime a client requests for posts, our API should return all Posts in the database.

    Finally, we must define the schema variable. Now we need to add an endpoint to our API so that we can test it in interactive mode.

    Create a new file in blog/urls.py and add the following code to it.

    from django.urls import path
    from graphene_django.views import GraphQLView
     
    urlpatterns = [
        path("graphql/", GraphQLView.as_view(graphiql=True)),
    ]

    To make this file discoverable we need to specify it in the root urls, open core/urls.py and add the following code.

    from django.contrib import admin
    from django.urls import path, include
     
    urlpatterns = [
        path('admin/', admin.site.urls),
        path("", include("blog.urls")),
    ]

    Code which is in bold is the newly added code. Now run the server using python manage.py runserver and go to http://127.0.0.1:8000/graphql, you should see the following screen.

    Query with GraphiQL

    Now we are all set to query our api, let's test the only query we defined that is posts, and see the output.

    Look at how well our API delivered all of our database's books; the greatest part is that we didn't have to provide all of the fields. To see how flexible our API is, run the following query.

    {
      posts{
        title,
      }
    }

    Doing the CRUD

    We've just used our GraphQL API to read data so far; let's take it a step further and have it do Create, Read, Update, and Delete operations.

    Parameters with GraphQL

    Let’s make our query to return the data according to the given parameters.

    We'll do this by creating a new query in the same query class. This new query will have a String type argument called search, which will be used to find the keyword in the titles of our posts. As previously said, every query must have a corresponding resolve function, else Graphene will have no idea what to return. To return the desired queryset, we used title__icontains in the resolve function.

    class Query(graphene.ObjectType):
        posts = graphene.List(PostType)
        post_search = graphene.List(PostType, search=graphene.String())
     
        def resolve_posts(self,info):
            return Post.objects.all()
        def resolve_post_search(self, info, search):
            return Post.objects.filter(title__icontains = search)

    Let’s test if this is working or not. Go to the http://127.0.0.1:8000/graphql and type the following query.

    Voila! Only posts with the letter 'A' in their titles are returned by our API. Note that our snake_case query does not work; we must write our query in camelCase. This is the schema's default behavior; you may override it by specifying auto_camelcase=False in the schema variable.

    Create New Data

    In GraphQL there are three types of operations - Query, Mutation and Subscription; to create a new record in our database we use Mutation Operation.

    The Mutation in GraphQL defines an input. Let's make a Mutation Type to understand it more clearly. The below mutation will create a new post using the post title and content. Open blog/schema.py file and add the following code to it before the schema declaration.

    from django.template.defaultfilters import slugify
     
    class PostMutation(graphene.Mutation):
        class Arguments:
            title = graphene.String(required=True)
            content = graphene.String(required=True)
       
        post = graphene.Field(PostType)
     
        @classmethod
        def mutate(cls, self, info, title, content):
            post = Post(title=title,content=content,slug=slugify(title))
            post.save()
            return PostMutation(post=post)
           
    class Mutation(graphene.ObjectType):
        create_new_post = PostMutation.Field()
     
    schema = graphene.Schema(query=Query, mutation=Mutation)

    In the above code the PostMutation describes the Arguments our Mutation will take and the mutate function consists of the plain Django code to create a new post and save it to our database using the ORM, after that the newly created post returns our GraphQL client side.

    The mutate function can be interpreted as the resolve function for Mutation operation.

    The Mutation class below describes the command that will be used when creating the new Post.

    After running the query the GraphQL is returning the newly created post to us. Note that we need to specify that it is a mutation query in the first line.

    Let’s also confirm this using the Django Administration using the superuser account.

    Update Existing Data

    The updation command will be very identical to the creation command, except we will utilize a unique identifier (Primary Key) as a parameter to identify the record that will be updated in this section. The code to update a post record is shown below.

    class UpdatePost(graphene.Mutation):
        class Arguments:
            id = graphene.ID()
            title = graphene.String(required=True)
            content = graphene.String(required=True)  
     
        post = graphene.Field(PostType)
       
        @classmethod
        def mutate(cls, self, info, id, title, content):
            post = Post.objects.get(id=id)
            post.title = title
            post.slug = slugify(title)
            post.content = content
            post.save()
            return UpdatePost(post=post)
     
    class Mutation(graphene.ObjectType):
        create_new_post = PostMutation.Field()
        update_post = UpdatePost.Field()

    We added our new command to the same Mutation class, as you can see in the code above. The mutate method just uses Django ORM to retrieve the post from the database, change it with new data, and save it back.

    This is how we will execute the above Mutation.

    Delete Data using GraphQL

    Before we look at the code below, I want you to attempt the Delete Mutation on your own because it'll be pretty similar to what we've done in the other mutations except for what we return to the client. We can't return a post object because it's already been deleted, so what do we return? I believe that sending a confirmation message to the client would be appropriate. Let's code Delete Mutation now that we've got that sorted.

    class DeletePost(graphene.Mutation):
        class Arguments:
            id = graphene.ID()
           
        msg = graphene.String()
       
        @classmethod
        def mutate(cls, self, info, id):
            post = Post.objects.get(id=id).delete()
            return DeletePost(msg = "Post deleted Successfully")
     
    class Mutation(graphene.ObjectType):
        create_new_post = PostMutation.Field()
        update_post = UpdatePost.Field()
        delete_post = DeletePost.Field()

    Lets execute this Mutation on the GraphiQL.

    You can check the Django Administration to see if it was successfully removed. Check Final Project Repository if you are facing any problems.

    Authentication using GraphQL + JWT

    Authentication is a critical component of every application, and it must be done correctly. To authenticate a user in an API, we usually use token authentication. Token Authentication works by a user entering their credentials and the server generating a token that is returned by the API after confirming whether the credentials are legitimate. That is the most basic explanation of how this works; if you want to learn more about it, I recommend reading this JWT introduction, which explains everything in detail.

    To use JWT authentication in django we use a package called graphql_auth, this package comes with all the tools, built-in queries and mutations to work with JWT.

    In this section we will touch the following topics.

    • User Registration
    • User Verification
    • User Authentication

    Setting up the project for JWT

    To start JWT in our django project we need to install a number of dependencies, if you want the starter template for this section you can clone the repository and follow these instructions to get started.

    Below is the overview of what each dependency does for us.

    django-graphql-jwt 0.3.0 - This library provides all the major mutations and queries to work with JWT in GraphQL.

    django-graphql-auth 0.3.15 - This library abstract all the logic for account handling with GraphQL, it  provides important Queries like MeQuery and UserQuery

    PyJWT 1.7.0 - This library already comes pre-installed with django-graphql-jwt but there are some issues with version 2.0, that is why we will explicitly install and use version 1.7.0.

    If you are cloning the starter template you can just run pip install -r requirements.txt to install all the dependencies as described in the readme.md.

    After installing the above dependencies we have to add them to our INSTALLED_APPS setting.

    INSTALLED_APPS = [
        'django.contrib.admin',
        . . . 
        . . .
        # 3rd Party apps
        'graphene_django',
        'graphql_jwt.refresh_token.apps.RefreshTokenConfig',
        'graphql_auth',
        'django_filters',
    ]

    In graphene, we'll also need to add a MIDDLEWARE option that instructs it to route all API requests through this middleware.

    GRAPHENE = {
        "SCHEMA": "blog.schema.schema",
        "MIDDLEWARE": [
            'graphql_jwt.middleware.JSONWebTokenMiddleware',
        ],
    }

    We must include GraphQL in our AUTHENTICATION BACKENDS configuration because we will be using it for authentication. In any Django app, the ModelBackend setting is enabled by default.

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

    For our API to send us Verification token we will use the console as an EMAIL_BACKEND since setting up an email service is beyond the scope of this article.

    EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'

    And finally we have to add the GRAPHQL_JWT setting which enables us to use graphql_auth’s user JWT Authentication classes.

    GRAPHQL_JWT = {
        'JWT_ALLOW_ANY_CLASSES':[
            "graphql_auth.mutations.Register",
            "graphql_auth.mutations.VerifyAccount",
            "graphql_auth.mutations.ObtainJSONWebToken",
        ],
        "JWT_VERIFY_EXPIRATION":True,
        "JWT_LONG_RUNNING_REFRESH_TOKEN":True,
    }

    Now that settings.py is complete, we must make a little adjustment to our URL before proceeding to our schema to create new queries and mutations.

    from django.views.decorators.csrf import csrf_exempt
     
    urlpatterns = [
        path("graphql/", csrf_exempt(GraphQLView.as_view(graphiql=True))),
    ]

    Now execute python manage.py migrate to update our database with the modifications made by the installed apps; after migrating, you should see something like this in your Django administration.

    User Queries in graphql_auth

    The library provides pre-built queries and mutations for user handling in our GraphQL API, which we only need to extend to our main query to be used by our API. To get started, edit the schema.py file and add the following code.

    from graphql_auth.schema import MeQuery, UserQuery
    class Query(UserQuery, MeQuery ,graphene.ObjectType):
        . . . 
        . . .

    It's important to keep in mind that we didn't write a resolve method because graphql_auth already has one. Let's put this to the test with GraphiQL.

    What exactly are edges and nodes, you might wonder? The graphql_auth chose to represent data in this manner, edges are the collection of all nodes, and each node represents one user.

    MeQuery is a graphql_auth feature that allows you to query the current authenticated user.

    User Mutations in graphql_auth

    This graphql_auth library includes some important mutations for user registration and authentication. To use the built-in mutations, write the following code in our schema.py file.

    class Mutation(graphene.ObjectType):
        create_new_post = PostMutation.Field()
        update_post = UpdatePost.Field()
        delete_post = DeletePost.Field()
        user_registration = mutations.Register.Field()
        user_verification = mutations.VerifyAccount.Field()
        user_authentication = mutations.ObtainJSONWebToken.Field()

    We don't need to do anything else because we had customised our settings.py file to use these mutations. Simply go to GraphiQL and try out this functionality.

    User Registration with JWT

    The mutation for user registration is shown below, which in this case was successful, indicating that the user was successfully created in the database.

    User Verification with JWT

    Your console should look something like this as soon as you run the above mutation.

    The preceding URL contains the token that will be needed to verify the user because we set up the console as our email backend. Now copy the token section of the URL from after 'activate/' to 'DA', excluding the '</p>'. This token will not be the same in your instance. This token will be required to validate our user.

    Now run this mutation to verify the user in our database. By running the UserQuery and retrieving the 'verified' status, you can see if it's been verified.

    User Authentication with JWT

    The unique token returned by the API must be stored in our cookie to authenticate the user on each request, and this token must be included in the request headers to identify the user, which is why if you run the MeQuery(shown in the above section) at this point, it will not return the user you just logged in.

    Using API Client for authentication

    We'll use an app called Insomnia to deliver our Authorization token but you can use any API client you prefer, which allows us to change our request headers.

    Download the application and re-login the user with the same query, copying the token received by the API this time.

    By going to the Header Tab in the left pane and adding a new Header "Authorization" with the value "JWT TOKEN>," we can now establish this token as an Authorization Header.

    Now use the MeQuery to get the details of Authenticated User.

    Voila, We are getting the details of the user which we just logged in. Check the final project directory if you are facing any problem.

    Conclusion

    In this article you learned how to set-up GraphQL in Django using Graphene. You also created a schema, custom queries and resolver functions. This article then introduced you to building the CRUD API functionality using GraphQL. We also covered the user authentication using the graphql_auth library, we utilized the built-in query and mutations in graphql_auth for user handling. At last we covered how to use an API client to use the Authorization token on the request headers to retrieve the user specific data.

    There's still a lot to like about graphene and graphql auth. To learn more about both the libraries and their features, check out their documentation.

    Data-rich bug reports loved by everyone

    Get visual proof, steps to reproduce and technical logs with one click

    Make bug reporting 50% faster and 100% less painful

    Rating LogosStars
    4.6
    |
    Category leader

    Liked the article? Spread the word

    Put your knowledge to practice

    Try Bird on your next bug - you’ll love it

    “Game changer”

    Julie, Head of QA

    star-ratingstar-ratingstar-ratingstar-ratingstar-rating

    Overall rating: 4.7/5

    Try Bird later, from your desktop