
Introduction
API is the abbreviation of Application Programming Interface. It means when two or more applications talk to each other they would use this intermediary program. Web API is an API which can be accessed over internet which is usually using HTTP or HTTPS protocol. GraphQL is one of its kind running over HTTP/HTTPS. (but its not hard and fast rule, it can be on any protocol)
Let me re-confirm GraphQL is not a protocol it is specification for client-server communication. It means specification is a description provided how client-server talks to understand each other, in lay man term its how we speak a language for our communication.
GraphQL is on the Web API kind. GraphQL is one of the most modern ways of building and querying APIs given the input in JSON format. It helps with a runtime for fulfilling those queries with your existing data.
Internet is all around right from system to watch, each serve different set of users serving different demands. Let's imagine we need to fulfil similar request but with different volume data based on the client. It would be inappropriate if we give less/underfetching or more/overfetching information to all. As a saviour GraphQL comes to serve clients only for the asked information without affecting coding changes in the server.
GraphQL is originally developed to query server hence its called GraphQL - Graph Query Language. One might wonder then why it is Graph, well the information stored in hierarchically is know as graph and using GraphQL you can drill down to every connection and get the node information (we will see below with an example).
GraphQL provides a complete and understandable description of the data in your API, gives clients the power to ask for exactly what they need and nothing more, makes it easier to evolve APIs over time, and enables powerful developer tools (GraphiQL).
Facebook's mobile apps have been powered by GraphQL since 2012.
Today we will see about GraphQL, its features, pros and cons.
GraphQL Features
Requirements and Installation
To start using graphql, we would need python 3+ version.
Python >= 3.4
Graphene (3.0) is the module used to the graphql.
Using pip you can easily install graphene.
pip install "graphene>=3.0"
GraphQL Structure
Fields
GraphQL Fields are generally the columns specified, which could be called in querying the values. GraphQL provides different types of Fields, such as ID, String, Scalar, List, Enum, Object, Interface and Unions.
Eg:
Query:
projects {
ProjectID
ProjectName
LOBDetails {
LOBID
LOBName
}
}
Output:
{
“data”: {
projects:
{
ProjectID: “J1”
ProjectName: “Jio”
LOBDetails:
{
LOBID: “AT”
LOBName: “Atul”
}
{
LOBID: “BA”
LOBName: “Bavesh”
}
}
}
}
From the above example, we have returned 1 project which is headed by 2 LOBs. Here the LOBID is ID data type and LOBName is string datatype. There would be other fields also but GraphQL will return only the asked details.
Resolver
Resolver is ideally a function associated with field which generates the response in JSON format for the GraphQL Query. And it would be only called in the query is requesting to view the value of the field. In the above example we have LOBDetails information which would be resolved while its called. Lets see the syntax with example code. Note the function should always start with resolve_<FieldName>
resolve_fieldName:(root, args, context, info) => { result }
Eg:
class Project(graphene.ObjectType):
LOBID = graphene.ID(required=True)
ProjectID = graphene.ID(required=True)
ProjectName = graphene.String()
ProjectDesc = graphene.String()
LOBDetails = graphene.List(LOB)
def resolve_LOBDetails(root, info):
lobid = root.get('LOBID', None)
return db.get_lob(lobid)
Schema
The GraphQL Schema is where the data model is described. GraphQL server provides set of resolve methods that know how and from where to fetch the data. Schema will be defined once in the program, which will collect all the type definitions. Later down we have an example, filename called schema.py which holds the sample schema definition.
Query
GraphQL Query can be said as Read operation. This where you can specify or we can query the API Fields. Even though query seems to be GET operation, but the actual POST HTTP Method is called for this. Class graphene.ObjectType needs to be inherited for framing the Query. Below we have specified the syntax of using query:
//syntax 1
query query_name{ someField }
//syntax 2
{ someField }
Mutation
GraphQL Mutation can be said as Write, Update and Delete operations. Here you will specify the values which needs to be changed, and response after performing the operation. Below we have specified the syntax of using query:
mutation{
someEditOperation( someInputSchema:
{
dataField1:"valueOfField1",
dataField2:"valueOfField2"
}
) {
returnValue1
returnValue2
}
}
Validation
Its one of the very useful feature provided by the module, any input either while querying or mutating the values the validation will be done. Validation such as not null, datatype validation, string length, range, and so on. Below we have any example where the LOBID is mandatory to be provided while insertion if not operation will fail.
Enum
GraphQL supports Enumerations. An enumeration is a set of symbolic names which are bound to unique and having constant values. In graphene we can add description as well to the Enum values using @property with description method.
Eg:
# Enum Definition
class Rating(graphene.Enum):
EXCELLENT = 1
GOOD = 2
OKAY = 3
@property
def description(self):
if self == Rating.EXCELLENT:
return 'Very Good Performance'
if self == Rating.GOOD:
return 'Good Performance'
return 'Okay Performance'
# Enum Usage as Field
class UpdateEmployee(graphene.Mutation):
EmpID = graphene.ID(required=True)EmpRating = Rating(required=True)
Interface
As like general language Interface in GraphQL also provides similar feature. It defines the abstract type that contains certain fields which can be later inherited to other Schemas. For example we have few common fields which we can define in single interface and use it across the respective schemas. Also we would need to write an resolver for this while querying.
Eg:
import graphene
Creation of Interface called AuditInformation
class AuditInformation(graphene.Interface):
CreatedBy = graphene.String()
ModifiedBy = graphene.String()
CreatedOn = graphene.Date()
# Need to define a resolve_type class method, else will return error. This is to map the interface date object to graphene.type
# Error: "Abstract type XXX must resolve to an Object type at runtime for field..."
# https://docs.graphene-python.org/en/latest/types/interfaces/#resolving-data-objects-to-types
@classmethod
def resolve_type(cls, instance, info):
if instance["Type"] == 'Project':
return Project
return LOB
# Calling the Interface as AuditDetails in LOB Schema
class LOB(graphene.ObjectType):
AuditDetails = graphene.Field(AuditInformation)
LOBID = graphene.ID(required=True)
LOBName = graphene.String()
def resolve_AuditDetails(root, LOBID):
return get_audit_details(LOBID)
# Calling the Interface as AuditDetails in Project Schema
class Project(graphene.ObjectType):
AuditDetails = graphene.Field(AuditInformation)
ProjectID = graphene.ID(required=True)
def resolve_AuditDetails(root, LOBID, ProjectID):
return get_audit_details(LOBID, ProjectID)
Lets see how to query the interfaces:
Query:
query{
project{
ProjectID
LOBID
AuditDetails{
CreatedBy
ModifiedBy
__typename
}
}
}
Result:
{
"data": {
"project": [
{
"ProjectID": "Proj1",
"LOBID": "123",
"AuditDetails": {
"CreatedBy": "Admin",
"__typename": "Project"
}
},
{
"ProjectID": "Proj2",
"LOBID": "123",
"AuditDetails": {
"CreatedBy": "Admin",
"__typename": "Project"
}
}
}
}
Union
Unions are like we have in dbs, but not that we will have combine different values in single row but still the values are get build in multiple rows. If you want to return more than one type of object they you can create union type, which creates an association between two different object types. In graphene package it must inherit from graphene.Union. Unions don’t have any fields on it, just links to the possible graphene.ObjectType.
Eg:
class Project(graphene.ObjectType):
class Meta:
interfaces = (AuditInformation, )
LOBID = graphene.ID(required=True)
ProjectID = graphene.ID(required=True)
ProjectName = graphene.String()
ProjectDesc = graphene.String()
LOBDetail = graphene.List(LOB)
def resolve_LOBDetail(root, info):
lobid = root.get('LOBID', None)
return db.get_lob(lobid)
AuditDetails = graphene.Field(AuditInformation)
def resolve_AuditDetails(root, info):
return get_audit(root, info)
class POC(graphene.ObjectType):
class Meta:
interfaces = (AuditInformation, )
LOBID = graphene.ID(required=True)
POCID = graphene.ID(required=True)
POCName = graphene.String()
POCDesc = graphene.String()
LOBDetail = graphene.List(LOB)
def resolve_LOBDetail(root, info):
lobid = root.get('LOBID', None)
return db.get_lob(lobid)
AuditDetails = graphene.Field(AuditInformation)
def resolve_AuditDetails(root, info):
return get_audit(root, info)
class LOBSolutions(graphene.Union):
class Meta:
types = (LOB, Project)
@classmethod
def resolve_type(cls, instance, info):
print(instance)
if instance.get("POCID"):
return POC
return Project
Lets see how to query the Unions:
Query:
query{
lobsolutions{
... on Project{
ProjectID
ProjectName
},
... on POC {
POCID
POCName
LOBDetail{
LOBID
LOBName
}
}
}
}
Results
{
"data": {
"lobsolutions": [
{
"ProjectID": "Proj1",
"ProjectName": "Project"
},
{
"ProjectID": "Proj2",
"ProjectName": "Project"
},
{
"POCID": "POCProj1",
"POCName": "POCProject",
"LOBDetail": [
{
"LOBID": "123",
"LOBName": "Test"
}
]
},
{
"POCID": "POCProj2",
"POCName": "POCProject",
"LOBDetail": [
{
"LOBID": "123",
"LOBName": "Test"
}
]
}
]
}
}
GraphQL Example
Easiest way to learn is through understand different examples. Lets try that out. Here we will try to create lob (line of business) information.
Source Code:
File: lob_schema.py
Description: This file holds the information about LOB schema.
File Contents:
import lob_db
# Schema Objects
# Inherited by graphene.ObjectType will be used by Query
class LOB(graphene.ObjectType):
# ID : Is a datatype, which refers to the primary key
# required=True -> Makes the LOBID mandatory to be given from the client
LOBID = graphene.ID(required=True)
LOBName = graphene.String()
LOBDesc = graphene.String()
# Inherited by graphene.InputObjectType will be used by Mutation
class LOBInput(graphene.InputObjectType):
LOBID = graphene.ID(required=True)
LOBName = graphene.String()
LOBDesc = graphene.String()
class CreateLOB(graphene.Mutation):
class Arguments:
lob_input = LOBInput(required=True)
ok = graphene.Boolean()
msg = graphene.String()
def mutate(root, info, lob_input=None):
ok, msg = db.insert_lob(lob_input)
return CreateLOB(ok=ok, msg=msg)
File: lob_db.py
Description:
This file holds database operations for lob
In the below example LOBS are saved in the array of dict
But in real use case it could be any database
But the return value should match the keys of our defined Schema
File Contents:
LOBS = [
{
'LOBID': "123",
'LOBName': "Test",
'LOBDesc': "Test Desc"
}
]
def get_lob(lobid=None):
result = LOBS
if(lobid):
result = filter(lambda lob: lob['LOBID'] == lobid, LOBS)
return result
def insert_lob(lob):
print(lob)
LOBS.append(lob)
return True, "Success"
File: schema.py
Description:
This file holds the maps between all the schema in one file.
We can have more than one sub schemas defined but will link here.
createlob is a field returned from CreateLOB
File Contents:
import lob_schema
import lob_db
class Mutation(graphene.ObjectType):
createlob = lob_schema.CreateLOB.Field()
class Query(graphene.ObjectType):
lob = graphene.List(lob_schema.LOB)
def resolve_lob(root, info):
return lob_db.get_lob()
schema = graphene.Schema(query=Query, mutation=Mutation)
File: app.py
Description:
This file defines the application's starting point
GrahpQL here is integrated with Flask
Flask server will run
/grahpql path will be used both for Query and Mutation
For both it will be POST Method
File Contents:
from flask import Flask
from flask_graphql import GraphQLView
from schema import schema
app = Flask(__name__)
app.debug = True
@app.route('/')
def index():
return '<p> Hello World</p>'
app.add_url_rule(
'/graphql',
view_func=GraphQLView.as_view(
'graphql',
schema=schema,
graphiql=True # for having the GraphiQL interface
)
)
if __name__ == '__main__':
app.run()
Execution GraphQL Server:
Execute: app.py
% python app.py
* Serving Flask app 'app' (lazy loading)
* Environment: production
WARNING: This is a development server. Do not use it in a production deployment.
Use a production WSGI server instead.
* Debug mode: on
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
* Restarting with watchdog (fsevents)
* Debugger is active!
Output:
Step 1:
Fig1: Root path, as per code it prints Hello World
Step 2:
Open the /graphql path.
This is the same path configured in the app.py, this can be any name though
Below will shown only if the graphiql is enabled in app.py

Fig 2: graphql path, shows Query and Mutation
Step 3:
Lets check the available Mutation, in our scenario we will have createlob
LOBInput is the values required to be inserted/updated/deleted - mutated

Fig3: createlob is listed specifying its input and return value
Step 4:

Fig4: Input values for LOB
Step 5:

Fig5: Query output of LOB
Step 6:
Mutation called to perform insert
Here LOBID and LOBName values are specified to be inserted
LOBDesc is not given, which will be considered as NULL.

Fig6: Mutation query to insert
Step 7:
After insert lets query again and see the output.
Inserted row should be visible.
List of executed queries will also be recorded in the History
Necessary fields can be used to extract.

Fig7: After Mutation, Query output
GraphQL Vs REST
Fig8 : GrapqhQL vs REST
Credits: https://www.mobilelive.ca/blog/graphql-vs-rest-what-you-didnt-know/
GraphQL Pros
A GraphQL schema offers an organisation a way to federate its entire API.
GraphQL calls are handled in a single round trip. Clients get what they request with no over fetching.
Strongly defined data types reduce miscommunication between the client and the server.
GraphQL allows an application API to evolve without breaking existing queries.
GraphQL Cons
GraphQL presents a learning curve for developers familiar with REST APIs.
GraphQL shifts much of the work of a data query to the server side, which adds complexity for server developers.
Caching is more complex than with REST.
No standard HTTP error codes supported.
URLs cannot be bookmarked, as the query is passed in the POST.
API maintainers have the additional task of writing maintainable GraphQL schema.
Conclusion
We have seen a very basic example of GraphQL. But it holds lot more concepts such as Enum, Subscription, Interfaces, Unions, etc. Below references provide very good insights in such concepts.
References