What is GraphQL?

Technology is evolving so fast. Every day, old technologies are being replaced by new ones. For many years, REST architecture was dominating web services and APIS. In 2012, Facebook developed GraphQL and open-sourced it in 2015. Since then, it is becoming so popular in the development world. GraphQL is a declarative, strongly typed, data-driven query language to build APIs.

With GraphQL, you can ask exactly what you need from the server with a single API endpoint. Moreover, It aggregates data from multiple related tables and sources going as much deep as needed. In addition, with a strong type checking mechanism, GraphQL helps developers to write more reliable, robust and bug-free codes.

Why GraphQL?

Why we should care about GraphQL? Here are some most important reasons to use GraphQL.

  • Single API: solution for everything. No need to manage a lot of APIs.
  • Strongly-Typed well-defined schema. No more API documentation.
  • Get exactly what you need. No more over-fetching and under-fetching.
  • Minimum network roundtrips. No more bandwidth wasting.
  • Language agnostic next-generation API tool. Very rich and fast-growing community.

REST vs GraphQL

Every technology has some pros and cons. RESTful architecture is still very matured and widely used. Where Graphql is growing rapidly and gaining ground over the REST quickly. Here we are covering a quick comparison of GraphQL and REST.

GraphQLREST
Self DocumentingYesNo
Learning curve DifficultModerate
Web cachingNo.
libraries built on it have caching support
Yes
PerformanceFastRound-trip requests
take up more time.
Development
Speed
RapidSlower
Data FetchingExactly what neededOver fetching /
Under fetching

Graphql with Python/Django

GraphQL is a language-agnostic API tool. Therefore, we can use it with any existing language like Python, Java, PHP, Go, Ruby etc. To use GraphQL with python we will use the Graphene-Python library. It can be easily integrated with Django. Graphene offers easy integration with another open-source library called Graphene-Django. So, we are using Graphene and Graphene-Django to implement GraphQL in this project.

1. Setup Django project

We will create a simple blog with very basic functionality. Let’s create the following –

  • A project directory called gqlblog
  • A Django project called morning_blog
  • An app within morning_blog named blog
# create the project directory 
mkdir gqlblog
cd gqlblog

Create a virtual environment called blog_env and activate it.

python3 -m venv morning_env

source ./morning_env/bin/activate # on windows morning_env\Scripts\activate 

The virtual environment is ready to use. Let’s install Django and configure it. We will use pip to install python packages.

pip install Django
pip install graphene_django

Now create the Django project and app.

django-admin.py startproject morning_blog
cd morning_blog # change the directory

# Create the app
django-admin.py startapp blog

Now Just sync the database.

python manage.py migrate

Let’s run the server.

python manage.py runserver

Copy this URL to your browser.

http://127.0.0.1:8000/

You should see the congratulations message.

Awesome. Everything looks ok. Open the project with your preferred IDE. We are using visual studio code. Go to morning_blog/settings.py and add blog app to INSTALLED_APPS as following.

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

Within the blog app, go to models.py file and create some simple models.

from django.db import models

class Author(models.Model):
    name = models.CharField(max_length=250)
    description = models.TextField() 


class Blog(models.Model):
    title = models.CharField(max_length=300)
    author = models.ForeignKey(Author, on_delete=models.CASCADE)
    body = models.TextField()  

Now run these commands to create the migrations.

python manage.py makemigrations
python manage.py migrate

Let’s create some test data and load it to the database. Create a directory called fixtures within blog app. Create a file inside it called blogs.json and copy the following data.

[
    {
        "model": "blog.Author",
        "pk": 1,
        "fields": {
          "name": "Guido van Rossum",
          "description": "Python Creator"
        }
    },
    {
        "model": "blog.Author",
        "pk": 2,
        "fields": {
          "name": "Ijharul Islam",
          "description": "Full stack developer."
        }
    },

    {
      "model": "blog.Blog",
      "pk": 1,
      "fields": {
        "author_id": 2,
        "title": "How to build graphql API with Django",
        "body": "Lorem ipsum is placeholder text commonly used in the graphic, print."
      }
    },
    {
        "model": "blog.Blog",
        "pk": 2,
        "fields": {
          "author_id": 2,
          "title": "Graphql Data validation in Django",
          "body": "Lorem ipsum is placeholder text commonly used in the graphic, print."
        }
    }
  ]
  

And run the following command

python manage.py loaddata blogs

You should see the following output in the terminal.

Installed 4 object(s) from 1 fixture(s)

2. Basic Setup for GraphQL with Django

Add graphene_django to the INSTALLED_APPS in the morning_blog/settings.py file:

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    
    # Third party apps 
    'graphene_django',

    # Custom apps
    'blog',
]

Now we need to add an API for graphql. Let’s update the urls.py in morning_blog directory.

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

And create another file within morning_blog directory called schema.py and add the following code.

import graphene

class Query():
    pass


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

Finally, go to the settings.py file and add the location of the schema.

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

3. Writing Schema with Graphene

Before writing schema, create a directory within blog app called schema.py and add following files.

  • Create a file called authors.py
  • Create another file called blogs.py

So, your final folder structure should like the following.

Let’s add some schema for authors and blogs. Add the following code to the authors.py file.

import graphene
from graphene_django.types import DjangoObjectType, ObjectType

from ..models import Author


class AuthorFields():
    name = graphene.String()
    description = graphene.String()
 

class AuthorType(DjangoObjectType, AuthorFields):
    class Meta:
        model = Author
    id = graphene.ID(required=True)


class AuthorInputType(graphene.InputObjectType, AuthorFields):
    id = graphene.ID()


class DeleteAuthorInputType(graphene.InputObjectType):
     id = graphene.ID(required=True)

And add the following schema to blogs.py file.

import graphene
from graphene_django.types import DjangoObjectType, ObjectType

from ..models import Blog
from .authors import AuthorType


class BlogFields():
    title = graphene.String()
    body = graphene.String()


class BlogType(DjangoObjectType, BlogFields):
    class Meta:
        model = Blog

    id = graphene.ID(required=True)
    author = AuthorType()


class BlogInputType(graphene.InputObjectType, BlogFields):
    id = graphene.ID()
    author_id = graphene.ID()


class DeleteBlogInputType(graphene.InputObjectType):
     id = graphene.ID(required=True)

4. Writing Queries

Here we will add some queries for the Author model. Add the following code to authors.py file.

class Query(ObjectType):
    author = graphene.Field(AuthorType, id=graphene.ID(required=True))
    authors = graphene.List(AuthorType)

    def resolve_author(self, info, **kwargs):
        id = kwargs.get("id")
        return Author.objects.get(id=id)
        
    def resolve_authors(self, info, **kwargs):
        return Author.objects.all()

Also, add some queries for Blog model. Add the following code to blogs.py file.

class Query(ObjectType):
    blog = graphene.Field(BlogType, id=graphene.ID(required=True))
    blogs = graphene.List(BlogType)
    
    def resolve_blog(self, info, **kwargs):
        id = kwargs.get("id")
        return Blog.objects.get(id=id)

    def resolve_blogs(self, info, **kwargs):
        return Blog.objects.all()

5. Creating Mutations

To perform Create/Update/Delete operations, we have to add mutations. Add the following mutations to authors.py file.

class CreateAuthor(graphene.Mutation):
    class Arguments:
        input = AuthorInputType(required=True)

    ok = graphene.Boolean()
    author = graphene.Field(AuthorType)

    @staticmethod
    def mutate(root, info, input):
        author = Author()
        for key, val in input.items():
            setattr(author, key, val)
        author.save()
        return CreateAuthor(ok=True, author=author)


class UpdateAuthor(graphene.Mutation):
    class Arguments:
        input = AuthorInputType(required=True)

    ok = graphene.Boolean()
    author = graphene.Field(AuthorType)

    @staticmethod
    def mutate(root, info, input):
        id = input.get("id")
        author = Author.objects.get(id=id)
        for key, val in input.items():
            setattr(author, key, val)
        author.save()
        return CreateAuthor(ok=True, author=author)


class DeleteAuthor(graphene.Mutation):
    class Arguments:
        input = DeleteAuthorInputType()

    ok = graphene.Boolean()

    @staticmethod
    def mutate(root, info, input):
        id = input.get("id")
        author = Author.objects.get(id=id)
        author.delete()
        return DeleteAuthor(ok=True)

Finally, add those mutations to Mutation class and register it to a schema.

class Mutation(graphene.ObjectType):
    create_author = CreateAuthor.Field()
    update_author = UpdateAuthor.Field()
    delete_author = DeleteAuthor.Field()

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

To create, update and delete the Blog, add the following code to blogs.py file. We are registering our mutations to a schema at the end.

class CreateBlog(graphene.Mutation):
    class Arguments:
        input = BlogInputType(required=True)

    ok = graphene.Boolean()
    blog = graphene.Field(BlogType)

    @staticmethod
    def mutate(root, info, input):
        blog = Blog()
        for key, val in input.items():
            setattr(blog, key, val)
        blog.save()
        return CreateBlog(ok=True, blog=blog)


class UpdateBlog(graphene.Mutation):
    class Arguments:
        input = BlogInputType(required=True)

    ok = graphene.Boolean()
    blog = graphene.Field(BlogType)

    @staticmethod
    def mutate(root, info, input):
        id = input.get("id")
        blog = Blog.objects.get(id=id)
        for key, val in input.items():
            setattr(blog, key, val)
        blog.save()
        return UpdateBlog(ok=True, blog=blog)


class DeleteBlog(graphene.Mutation):
    class Arguments:
        input = DeleteBlogInputType(required=True)

    ok = graphene.Boolean()

    @staticmethod
    def mutate(root, info, input):
        id = input.get("id")
        blog = Blog.objects.get(id=id)
        blog.delete()
        return DeleteBlog(ok=True)


class Mutation(graphene.ObjectType):
    create_blog = CreateBlog.Field()
    update_blog = UpdateBlog.Field()
    delete_blog = DeleteBlog.Field()

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

6. Register the schema to the project.

Now we have to update our root schema file. Go to morning_blog/schema.py and update it as bellow.

import graphene

from blog.schema.authors import schema as author_schema
from blog.schema.blogs import schema as blog_schema


class Query(author_schema.Query, blog_schema.Query):
    pass


class Mutation(author_schema.Mutation, blog_schema.Mutation):
    pass
    
schema = graphene.Schema(query=Query, mutation=Mutation)

We are done. It’s time to test our API.

7. Testing the GraphQL API

To test the API, copy this URL to your browser.

http://127.0.0.1:8000/graphql

Test Queries

Copy the following code to graphiql console and run it.

query {
  
  blogs{
    id
    title
    author {
      id
      name
    }
  }
  
}

You should see the following outputs.

To get a single blog, write the following query with a specific id.

query{
  
  blog(id: 1){
    id
    title
    author {
      id
      name
    }
  }
  
}

You will see the following result.

Test Mutations

Let’s create a blog item with the author.

mutation createBlog {
  createBlog(input: {
    title: "How to build GraphQL API with Django.",
    authorId: 1,
    body: "GraphQL is a declarative, strongly typed, data-driven query language to build APIs."
  }) {
    ok
    blog {
      id
      title
      author {
        id
        name
      }
      body
    }
  }
}

You should see the following result.

Let’s update the previous blog title.

mutation updateBlog {
  updateBlog (input: {
    id: 4,
    title: "How to build GraphQL API with Django - 7 steps.",
    authorId: 1,
    body: "GraphQL is a declarative, strongly typed, data-driven query language to build APIs."
  }) {
    ok
    blog {
      id
      title
      author {
        id
        name
      }
      body
    }
  }
}

We just updated the title. Check out the updated result.

Awesome. We did lot of things. One thing is left. Let’s delete a blog item.

mutation deleteBlog {
  deleteBlog (input: {
    id: 4
  }) {
    ok
  }
}

You will see the following output.

Conclusion

We created a simple Django blog with Graphene and Django-graphene. To use GraphQL, we designed API schema, queries, and mutations. In the end, we tested the CRUD functionality with GrpahiQL console.

Here is the source code of the complete project. You can check it out from here.