Using GraphQL in your Python Django application
February 22nd, 2021

Comments

Using GraphQL in your Python Django application

Using GraphQL with DjangoREST API has been the most popular architectural style for designing Application Programming Interfaces (APIs). It provided better efficiency, increased scalability & improved performance to its counterpart SOAP.

However, REST API encounters a few major drawbacks as the app complexity grows:

  • Inflexible Structure

    • In the REST API paradigm Server defines the structure of the data which leaves the Client completely inflexible to changes
  • Strong Coupling between Client-Server

    • The only way for the Client to access some data is if the server has an exposed endpoint for it.
  • Under-fetching/ Over-fetching

    • As the structure defined by the Server is fixed and inflexible, Client is faced with problems like over fetching or under fetching of data
  • Multiple API calls

    • Requesting correct and complete data in REST API structure frequently results in multiple roundtrip API calls which reduces performance over time

The solution for all the above problems is GraphQL! As per docs

But what is GraphQL?

As per GraphQL docs

GraphQL is a query language for your APIs

What it means is unlike REST API, GraphQL uses Graph to represent your data. Each node in the Graph represents your Data model.

Why you should use GraphQL?

  • Unlike REST API which exposes multiple endpoint to clients, GraphQL exposes only a single endpoint
  • Clients can call this single endpoint & ask for the exact data they need
  • Server provides clients the exact requested data, No more No less!
  • Clients can declaratively fetch data from the server

In short,

GraphQL is the right choice for you if you have frequently changing data requirements in your application

So, how do you implement GraphQL?

  • GraphQL has both server side and client side implementation
  • GraphQL server defines the Graph of Data models also called Schema which is then declaratively consumed by GraphQL clients(Web, Mobile Apps etc)

Now let’s see how we can implement GraphQL in Python Django application

How to implement GraphQL in your Django Application?

Graphene-Python is a library for building GraphQL APIs in Python easily.

For implementing GraphQL in Django, we are going to use one of  Graphene-Python’s integration called Graphene-Django.

Blogging app with Django + GraphQL

To truly see the power of GraphQL in Django lets create a Blogging app having CRUD functionality.

Our app will have following features:

  1. Feed of all the Posts in Descending Order
  2. Individual Post details
  3. List of Authors
  4. Individual Author details
  5. List of Posts of a particular Author
  6. Add new Post
  7. Add new Author

If we were to design this type of system in REST API it would look something like this

  • GET requests
    • /posts
    • /posts/:id
    • /authors
    • /authors/:id
    • /authors/:id/posts
    • /posts/:id/authors/:id
  • POST requests
    • /authors
    • /posts

Now let’s design the same functionality in GraphQL.

Note: We are focusing on GraphQL server side implementation & to mimic GraphQL client we are going to use GraphiQL tool which allows us to test GraphQL API

To implement this we will implement the following steps
  • Create Blog project in Django
  • Define Blog Models
  • Install Graphene-Django
  • Create /graphql url
  • Define GraphQL Types
  • Create Queries and Mutations
  • Define Schema
  • Test using GraphiQL

Create Blog Project in Django

Firstly let’s create a directory called blogsite. Go to your terminal & run

> mkdir blogsite
> cd blogsite

Then we will create a virtual environment named env for our Django Project

> virtualenv env

Let’s activate our virtual environment named env

> source env/bin/activate

Post that we’ll install Django dependency

> pip install django

Once done we’ll create a Django project named blogsite in our current directory

> django-admin startproject blogsite .

We will also create a Django app called blog

> django-admin startapp blog

Let’s register our newly created blog app. Go to blogsite/settings.py and add blog to INSTALLED_APPS

...
INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'blog'
]
...

Once done we will execute migrations to create default Models in Database(SQLite)

> python manage.py migrate

Finally, let’s run our server

> python manage.py runserver

Now, open http://127.0.0.1:8000/ in your browser. Everything is great if you see screen like this ?

Django home page

Next, we will create our models.

Creating Models

For our Blogging App we will need 2 models

  • Post
  • Author

Let’s start!

Firstly, go to blog/models.py

from django.db import models
from django.utils.timezone import now # Create your models here. class Author(models.Model): name = models.CharField(max_length=100) biodata = models.TextField() def __str__(self): return self.name class Post(models.Model): title = models.CharField(max_length=100) content = models.TextField() created_at = models.DateField(default=now) author = models.ForeignKey(Author, on_delete=models.CASCADE) def __str__(self): return self.title

So let’s dissect exactly what’s happening in the above code

  • We are creating 2 models namely Author and Post
  • Author model has 2 fields, name and  biodata
  • Post model has title, content, created_at and a foreign key author

Next, go to blog/admin.py to register our models

from django.contrib import admin
from .models import Author, Post # Register your models here.
admin.site.register(Author)
admin.site.register(Post)

Let’s migrate & commit these models to our database. Go to your terminal & run

> python manage.py makemigrations > python manage.py migrate

Let’s also load some sample data. Copy this sample blog.json to blog/fixtures/blog.json and run this command in your terminal

> python manage.py loaddata blog

Great job! Now, let’s start with our GraphQL implementation.

Installing Graphene-Django

In your terminal run

> pip install graphene-django 

Next, add graphene_django to INSTALLED_APPS. Go to blogsite/settings.py

...
INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'blog', 'graphene_django'
]
...

Creating /graphql URL

When working with GraphQL, the only endpoint/API exposed to client is /graphql. Client can request and update data through this and only this endpoint. So then and there, we have less endpoints to maintain as compared to REST

To do so, go to blogsite/urls.py and create a new URL

from django.contrib import admin
from django.urls import path
from django.views.decorators.csrf import csrf_exempt
from graphene_django.views import GraphQLView urlpatterns = [ path('admin/', admin.site.urls), path('graphql', csrf_exempt(GraphQLView.as_view(graphiql=True)))
]
  • Firstly we are adding a new URL called graphql.
  • There is a special View provided by Graphene-Django called GraphQLView which will be executed when graphql url is hit.
  • graphiql=True allows us to use GraphiQL, an in built IDE. It makes testing of our GraphQL queries very easy!

    Note: If you are using CSRF protection in your Django app then, it will prevent your client from POSTing to this new graphql/ url. To avoid that we are using csrf_exempt()

    Currently if you go to http://127.0.0.1:8000/graphql  you will see an ERROR page. This is because we haven’t yet provided Schema for our graphql.

But what is a Schema?

    • GraphQL represents data in the form of Graph wherein each node represent a data model. GraphQL Schema represents this!
    • Schema is a contract/agreement for client-server communication
    • GraphQL Schemas are written in its own custom Type Language called SDL (Schema Definition Language)
    • Every graph needs a root node from where the access begins. In GraphQL Schema, the root node can be a
      • Query class which is similar to GET method in REST API
      • Mutation class which is similar to POST, PUT and DELETE methods
      • Subscriptions class similar to pub-sub model
    • Each of these classes represents different GraphQL Types

What are GraphQL Types?

GraphQL Types defines what kind of object and its corresponding properties you fetch. A simple example would be something like this

type Author { name: String! biodata: String! posts: [Post!]
}

Here we can fetch Author type object and requests for fields like name, biodata and posts

Let’s define GraphQL Types for our Blogging app

Defining GraphQL Types

Because we are using Graphene, it provides us a way to convert our Models(Author, Post) into Object Types a.k.a GraphQL Types. All we have to do is subclass DjangoObjectType and specify our model.

Let’s create types for our Author & Post model

Inside blogsite, create a new file named types.py

from graphene_django import DjangoObjectType, DjangoListField
from blog.models import Author, Post class PostType(DjangoObjectType): class Meta: model = Post fields = ("id", "title", "content", "author", "created_at") class AuthorType(DjangoObjectType): class Meta: model = Author fields = ("id", "name", "biodata") posts = DjangoListField(PostType) def resolve_posts(self, info): return Post.objects.filter(author=self.id)

Let’s see what’s happening above:

  • We first imported DjangoObjectType from graphene_django which converts our Django Models to GraphQL types.
  • We also imported DjangoListField which we’ll use to lists the Posts created by an Author
  • Next, from our blog app we are importing Author, Post models
  • We then created a class PostType which subclasses DjangoObjectType.
    • To specify Post model, inside class Meta we will just add Post to model property
    • fields is used to specify which columns to include as fields in GraphQL Type.
      • Specify required columns as a tuple
      • To include all fields just assign __all__ but this is not recommended
  • We did the same thing for AuthorType. In addition to that
      • We also added an extra field posts which was not originally present in our Model.
      • Next, we need to explicitly tell GraphQL how to get data for this field as it was not originally present in our Model
      • We use resolvers for that.
      • To create a resolver, we use the following syntax
    def resolve_<fieldname>(self, info, **kwargs): return <data>
    

Note:

  • All GraphQL custom fields need a corresponding resolver function
  • All your Type names should be Capital Case

Now let’s import these types and define our Queries

Defining Queries

  • GraphQL Query object is used to fetch data
  • It’s similar to GET method in REST API
  • It is also called Root Query, which defines the fields that acts as entry point in GraphQL API
  • Providing Root Query is mandatory when defining Schema

To create a Query node,

Go to blogsite and create a new file called queries.py

import graphene
from .types import AuthorType, PostType
from blog.models import Author, Post class Query(graphene.ObjectType): feed = graphene.List(PostType) post = graphene.Field(PostType, postId=graphene.String()) all_authors = graphene.Field(AuthorType) author = graphene.Field(AuthorType, authorId=graphene.String()) # Resolver for feed field def resolve_feed(parent, info): return Post.objects.all().order_by('created_at') # Resolver for post field def resolve_post(parent, info, postId): return Post.objects.get(id=postId) # Resolver for all_authors field def resolve_all_authors(parent, info, postId): return Author.objects.all() # Resolver for author field def resolve_author(parent, info, authorId): return Author.objects.get(id=authorId)
  • Each variable i.e feed, post, all_authors, author corresponds to a GraphQL query
  • feed & all_authors returns a List of PostType and AuthorType objects respectively
  • post author return Objects of  PostType and AuthorType respectively
  • resolvers for all these fields is just fetching the data from the Models and returning it

So far, we only covered the fetching of data but we also need a way to change the data. We use Mutations for that.

Defining Mutation

  • Mutation in GraphQL is used for changing, deleting or adding new data
  • It is also one of the root nodes also known as Root Mutation
  • It’s similar to POST, DELETE or PUT methods in REST API

So, for our blogging application we need mutations for

  • Adding New Post
  • Deleting a Post
  • Adding a New Author

Creating Mutation requires 2 steps

  • Defining Input Object Types
  • Creating Mutations

Defining Input Object Types

Input Object Types represents the argument structure required for a mutation.

To create one, go to blogsite/types.py

We’ll create InputObjectType for our different mutations below our existing code

Creating new post

...
class PostInput(graphene.InputObjectType): id = graphene.ID() title = graphene.String(required=True) content = graphene.String(required=True) created_at = graphene.Date() author_id = graphene.String(required=True, name="author")
  • PostInput defines the variables required as Arguments to create new post
  • The mandatory arguments are title, content, author 

Note:

  • Because we specified name=”author”, the client refers to this argument as author instead of author_id

Similarly, we’ll define an AuthorInput type for adding new author

Adding new author

class AuthorInput(graphene.InputObjectType): id = graphene.ID() name = graphene.String(required=True) biodata = graphene.String()

Now, the next step is to actually define what our individual mutation will do.

Creating Mutations

Go to blogsite/ and create a new file mutations.py

Our Add New Post mutation would look something like this

class AddPost(graphene.Mutation): class Arguments: input = PostInput(required=True) post = graphene.Field(PostType) def mutate(parent, info, input=None): if input is None: return AddPost(post=None) _post = Post.objects.create(**input) return AddPost(post=_post)
    • To define mutation we need to subclass  graphene.Mutation
    • input field in Arguments class corresponds to the arguments required for our mutation.  Here we are saying that the structure for the input  argument is of type PostInput
    • mutate() is a special resolver which executes once our mutation is called. Update logic for Models is present here
    • post  is the output field to be returned
    • So our structure would look something like this
addPost(input: PostInput) -> (post: PostType)

Let’s add a similar mutation for Add New Author. It’s structure would look something like this

addAuthor(input: AuthorInput) -> (author: AuthorType)

class AddAuthor(graphene.Mutation): class Arguments: input = AuthorInput(required=True) author = graphene.Field(AuthorType) def mutate(parent, info, input=None): if input is None: return AddAuthor(author=None) _author = Author.objects.create(**input) return AddAuthor(author=_author)

The last thing to do here is to define these mutation as Fields in Root Mutation. Below our existing code add this

class Mutation(graphene.ObjectType): add_post = AddPost.Field() add_author = AddAuthor.Field()

Defining Schema

Once the fields on Query class & Mutation Class are defined, all we need to do is pass this to GraphQL Schema.

To define schema, go to blogsite and create a new file schema.py

import graphene
from .queries import Query
from .mutations import Mutation schema = graphene.Schema(query=Query, mutation=Mutation)

Finally, just specify Schema location in blogsite/setting.py

GRAPHENE = { 'SCHEMA':'blogsite.schema.schema'
}
  • Schema() from Graphene creates GraphQL Schema with query argument as the Query class which we created
  • Now, all the fields like feed, author, allAuthors, post  can be accessed through our /graphql endpoint

Testing with GraphiQL

Open http://127.0.0.1:8000/graphql/ to see GraphiQL view

Testing Query

Let’s test query feed field using GraphiQL. Paste below query on the left side and click Play button

{ feed { title createdAt content author { name biodata posts { title } } }
}

It should display something like this

GraphiQL Queries Example

Testing Mutations

You can execute mutations like this

{
# $input is query variable
# addPost() is the mutation we created
mutation addPost($input: PostInput!) { addPost(input: $input) { # post is the output field defined in AddPost mutation  post { id title content author { name } } }
}

Response would look something like below

GraphiQL Mutation

You can similarly tests other queries and mutation which we defined.

Voila your first GraphQL + Django application is Done??

You can get the entire blogging app source code here

Conclusion

GraphQL in Django can result in an application which is extremely scalable & flexible if implemented correctly. The most important thing here is to learn how to map your Data Models in the form of Graphs. This guide is just a sneak peak of power GraphQL+Django.

Hope you enjoyed! You can connect with me on LinkedIn, Instagram, Twitter!

Resources

  • https://docs.graphene-python.org/en/latest/types/mutations/
  • https://www.howtographql.com/
  • https://docs.graphene-python.org/projects/django/en/latest/
  • https://graphql.org/learn/thinking-in-graphs/
Arwa Lokhandwala
Hi, I am Arwa!! I love to explore new things going around in the web world and write about it. I am also a speaker & an Instructor. When I am not exploring tech world you can probably see me reading a non-fiction book or watching some science-fiction series !!

Tags: backend, DJANGO, Graphene, Graphene-Django, GRAPHQL, python