GraphQL & Python – A Beauty in Simplicity, A Beast in Application

Last Updated on: July 14, 2020

GraphQL & Python – A Beauty in Simplicity, A Beast in Application

Introduced by Facebook in February 2015, GraphQL is a query language for APIs and a runtime for fulfilling those queries with your existing data. It provides a complete, understandable description of the data in your API while giving clients the ability to ask for exactly what they need and nothing more.  This gives you the freedom to evolve your APIs over time while accessing powerful developer tools.

Let’s take a closer look at how to connect and fetch data from GraphQL as well as create and update records from Django ORM to graphene object type.

How to Use GraphQL with Django – Getting Started

Setting Up a Django Project
First, let’s start from scratch with a new Django project. Create virtualenv and activate it, then install Django and Graphene. Graphene_django is a Python package that serves Django as GraphQL.

virtualenv env
source env/bin/activate

pip install django
pip install graphene_django

# Setup new project with single app.

django-admin.py startproject graphcool
cd graphcool
python manage.py startapp graphapp

After generating the app, add the graphapp and graphene_django in installed_apps, then sync the database for the first time:

INSTALLED_APPS = [
       ...,
       “graphene_django”,
       “graphapp”,
]

python manage.py migrate

Next, let’s create a Django model in graphapp/models.py

from __future__ import unicode_literals
from django.db import models
from django.utils import timezone

class Author(models.Model):
     first_name = models.CharField(max_length=32)
     last_name = models.CharField(max_length=32)
     email = models.EmailField()
     created_at = models.DateTimeField(auto_now=True)

     def __str__(self):
          return self.first_name

Next we’ll be setting up a GraphQL backend. To do this, create a file called schema.py inside the app. This is equal to urls.py in the Django project. We’ll define all our queries here and they’ll be handled by the GraphQL backend.

Let’s start with a simple query, like retrieve (get in Django) and list (objects.all in Django)

import graphene
import datetime

from graphene_django.types import DjangoObjectType
from graphapp.models import Author

class AuthorType(DjangoObjectType):
     class Meta:
          model = Author

class Query(graphene.AbstractType):
     all_authors = graphene.List(AuthorType)
     author = graphene.Field(AuthorType, id=graphene.Int())

     def resolve_all_authors(self, args, context, info):
          return Author.objects.all()

     def resolve_author(self, args, context, info):
          id = args.get('id')
          if id is not None:
               return Author.objects.get(pk=id)
          return None

This schema file is actually an app level schema. Here, the  AuthorType class is similar to the serializer file in the Django rest framework which is  consolidated into a Query class of AbsractType. The reason for using AbstractType is to allow inheritance later by the Root Query class which will be created in our project level schema file.

So now let’s create our project level schema file (root schema) in graphcool folder — like this: graphcool/schema.py

import graphene
import graphapp.schema

class Query(graphapp.schema.Query, graphene.ObjectType):
     pass
schema = graphene.Schema(query=Query)

Now, update settings file again to enable GraphQL. Open graphcool/settings.py file and add the following to the file.

GRAPHENE = {
     'SCHEMA': 'graphcool.schema.schema'
}

Next, import the GraphQLView function from graphene_django views and add it to urls.py file so that queries can be received by django backend.

from django.conf.urls import url

from django.contrib import admin
from graphene_django.views import GraphQLView

urlpatterns = [
     url(r'^admin/', admin.site.urls),
     url(r'^graphql', GraphQLView.as_view(graphiql=True)),
]

Run server and open localhost:8000/graphql

You will now be able to see the interactive query window in your browser as shown above.

Try the following query in the browser

{
allAuthors{
     id
     firstName
     lastName
   }
}

You will get a response that looks something like this

{
 "data": {
   "allAuthors": [
     {
       "id": "1",
       "firstName": "Pawan",
       "lastName": "Sharma"
     },
     {
       "id": "2",
       "firstName": "Shishir",
       "lastName": "sharma"
     },
     {
       "id": "3",
       "firstName": "shirish",
       "lastName": "sharma"
     }
   ]
 }
}

 

In this example, I’m getting many record details because I have created them from the admin panel. You will get an empty list, so don’t be alarmed if your example output looks different.

Next, let’s start writing mutation for creating new records

Update the app level schema file as follows

import graphene
import datetime

from graphene_django.types import DjangoObjectType
from graphapp.models import Author

class AuthorType(DjangoObjectType): class Meta: model = Author class AddAuthor(graphene.ClientIDMutation): author = graphene.Field(AuthorType) class Input: first_name = graphene.String() last_name = graphene.String() email = graphene.String() @classmethod def mutate_and_get_payload(cls, input, context, info): author = Author( first_name = input.get(‘first_name’), last_name = input.get(‘last_name’), email =  input.get(’email’), created_at = datetime.datetime.now(), ) author.save() return AddAuthor(author=author) class AuthorMutation(graphene.AbstractType): add_Author = AddAuthor.Field() class Query(graphene.AbstractType): all_authors = graphene.List(AuthorType) author = graphene.Field(AuthorType, id=graphene.Int()) def resolve_all_authors(self, args, context, info): return Author.objects.all() def resolve_author(self, args, context, info): id = args.get(‘id’) if id is not None: return Author.objects.get(pk=id) return None

Also update the project level schema, and create a new class called RootMutation as follows:

import graphene
import graphapp.schema

class Query(graphapp.schema.Query, graphene.ObjectType):
     pass

class RootMutation(graphapp.schema.AuthorMutation, graphene.ObjectType):
     pass

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

Let’s run the following query to add new records in Author.

mutation{
  addAuthor(input:{firstName:"Mahendra",lastName:"Dhoni",email:"mahi@example.com"}){
      author{
            id
            firstName
            lastName
            email
              }
            }
        }

This is the response for the above query

{
"data": {
          "addAuthor": {
              "author": {
                   "id": "4",
                   "firstName": "Mahendra",
                   "lastName": "Dhoni",
                   "email": "mahi@example.com"
                    }
                  }
              }
         }

Now that we have seen how to create a new record in our database, let’s try to update the record.

In order to do this, we need to create a new mutation method that allows us to update the author information.

 

Add the following code in graphapp/schema.py

import graphene
import datetime

from graphene_django.types import DjangoObjectType
from graphapp.models import Author

class AuthorType(DjangoObjectType):
     class Meta:
          model = Author

class AddAuthor(graphene.ClientIDMutation):
     author = graphene.Field(AuthorType)
     class Input:
          first_name = graphene.String()
          last_name = graphene.String()
          email = graphene.String()

     @classmethod
     def mutate_and_get_payload(cls, input, context, info):
          author = Author(
            first_name = input.get('first_name'),
            last_name = input.get('last_name'),
            email =  input.get('email'),
            created_at = datetime.datetime.now(),
           )
          author.save()
          return AddAuthor(author=author)

class UpdateAuthor(graphene.ClientIDMutation):
     author = graphene.Field(AuthorType)
     class Input:
          id = graphene.String()
          first_name = graphene.String()
          last_name = graphene.String()
          email = graphene.String()

     @classmethod
     def mutate_and_get_payload(cls, input, context, info):
          author = Author.objects.get(pk=input.get('id'))
          if input.get('first_name'): author.first_name=input.get('first_name')
          if input.get('last_name'): author.last_name=input.get('last_name')
          if input.get('email'): author.email=input.get('email')
          author.save()
          return UpdateAuthor(author=author)

class AuthorMutation(graphene.AbstractType):
     add_Author = AddAuthor.Field()
     update_author = UpdateAuthor.Field()

class Query(graphene.AbstractType):
     all_authors = graphene.List(AuthorType)
     author = graphene.Field(AuthorType, id=graphene.Int())

     def resolve_all_authors(self, args, context, info):
          return Author.objects.all()

     def resolve_author(self, args, context, info):
          id = args.get('id')
          if id is not None:
               return Author.objects.get(pk=id)
          return None

The update query is somewhat different than the query for creating a record because it takes the ID of the record to be updated.
So let’s change the query below with input data to be updated.

mutation {
        updateAuthor(input: {id: "2", firstName: "Shishir"}) {
          author {
                id
                firstName
                lastName
                createdAt
                }
              }
           }

You will get the following response:

{
"data": {
        "updateAuthor": {
            "author": {
            "id": "2",
            "firstName": "Shishir",
            "lastName": "sharma",
            "createdAt": "2017-06-13T11:46:39.461389+00:00"
         }
       }
    }
}

Should I Use GraphQL over REST?

  • The REST server is about a list of endpoints, while the GraphQL server is like a type system.  We define a series of types (in this case, Author but there can be many more types) and the relations between them.

  • GraphQL itself is a simple database language designed to reflect the relationships between these defined types and express fields of interest on each object. This in turn makes GraphQL inherently self-documenting.

  • If, however, you plan to make the API public or collaborative, REST is probably a better option since more developers will be accustomed to working with it.

Stay tuned with us for more such insightful updates.

Saharsh Goyal

July 5, 2017

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *