GraphQL — Another flavour in a world of APIs

GraphQL

Supported Languages

Schema

Resolver

Operations in GraphQL

  • querya read-only fetch.
  • mutation an operation followed by a fetch.
  • subscriptionis a long-lived request that fetches data in response to source events.

Queries

query {
health
}

Mutations

// Example of a mutation to create a person...
mutation {
add_person (fullName: "...", age: 25) {
person {
uuid
fullName
age
}
}
}

Subscriptions

Introspection

{
__schema {
queryType {
name
fields {
name
description
}
}
mutationType {
name
description
fields {
name
description
}
}
}
}
{
"data": {
"__schema": {
"queryType": {
"name": "Query",
"fields": [
{
"name": "health",
"description": null
},
{
"name": "ticket",
"description": null
},
...
]
},
"mutationType": {
"name": "Mutations",
"description": null,
"fields": [
{
"name": "create_ticket",
"description": "It creates a new Ticket..."
},
...
]
}
}
}
}
{
__type(name: "Ticket") {
name
fields {
name
description
type {
name
}
}
}
}

Authentication | Authorization

GraphQL over REST

  • Standardization: Frequently developers don’t find the way to follow the REST principles and create very bad RESTful APIs (Don’t blame REST for this). In the GraphQL environment, the specification is defined.
  • Development Environment: GraphQL provides query validation, code completion as well as documentation. But, take into consideration that the documentation level depends on the Backend, everything is not by magic.
  • Retrieve only what you want: You are able to request/retrieve the exact information you need. Taking advantage of the documentation, validation and code completion, you can get the fields and attributes (You could implement the same result in REST) and know the type of those elements.
  • Lower Network Overhead: You can get everything you want using only one request.
  • One version: It’s append/update only and you don’t need to manage new versions.
  • One Access Point: You have only one URL to manage all your resources and actions.

Graphene

A Dummy Project — with Graphene

graphql-python-example
├── README.md
├── requirements.txt
├── server.py
├── service
│ ├── controllers.py
│ ├── __init__.py
│ ├── models.py
│ ├── mutations.py
│ └── schema.py
└── test.py
virtualenv --python=python3.8 .venv
source .venv/bin/activate
pip install --upgrade pip
pip install urllib3 graphene flask_graphql flask
# server.py
# -*- coding: utf-8 -*-

from flask import Flask
from flask_graphql import GraphQLView

from service.schema import schema

app = Flask(__name__)


@app.route("/")
def health():
return "OK"


app.add_url_rule(
"/graphql",
view_func=GraphQLView.as_view(
"graphql",
schema=schema,
graphiql=True)
)


if __name__ == "__main__":
app.run(port=3001)
# models.py
# -*- coding: utf-8 -*-

from graphene import String, Int, ObjectType


class Ticket(ObjectType):
name = String()
description = String()
story_points = Int()


class Person(ObjectType):
uuid = String()
full_name = String()
age = Int()
# mutations.py
# -*- coding: utf-8 -*-

from datetime import datetime, timedelta
from graphene import Mutation, String, Int, Field
from service.models import Person

from .controllers import controller


class CreateTicket(Mutation):
class Input:
name = String()
description = String()
story_points = Int()

expected_dateline = String()

@staticmethod
def mutate(root, info, name, description, story_points):
expected_dateline = datetime.utcnow() + timedelta(days=story_points)
return CreateTicket(expected_dateline.strftime("%Y-%m-%d %H:%M:%S"))


class AddPerson(Mutation):
class Input:
full_name = String()
age = Int()

person = Field(lambda: Person)

@staticmethod
def mutate(root, info, full_name, age):
return AddPerson(controller.add_person(full_name=full_name, age=age))


class RemovePerson(Mutation):
class Input:
uid = String()

person = Field(lambda: Person)

@staticmethod
def mutate(root, info, uid):
return RemovePerson(controller.remove_person(uid))
# controllers.py
# -*- coding: utf-8 -*-

from random import randint
from typing import Dict
from uuid import uuid4

from service.models import Person


class DataController:
people: Dict[str, Person] = {}

def __init__(self):
for x in range(10):
uuid_ = str(uuid4())
self.people[uuid_] = Person(
uuid=uuid_,
full_name=f"some_name_{x}",
age=randint(25, 50)
)

def add_person(self, full_name: str, age: int):
uuid_ = str(uuid4())

record = Person(
uuid=uuid_,
full_name=full_name,
age=age
)

self.people[uuid_] = record
return record

def get_by_id(self, uuid_: str):
return self.people.get(uuid_, None)

def get_all(self, limit: int = 10, offset: int = 0):
data = list(self.people.values())
end, size = limit + offset, len(data)
return data[offset:end if end < size else size]

def remove_person(self, uuid_: str):
return self.people.pop(uuid_, None)


controller = DataController()
# schema.py
# -*- coding: utf-8 -*-

from graphene import Field, ObjectType, Schema, String, List, Int

from service.controllers import controller
from service.models import Ticket, Person
from service.mutations import CreateTicket, AddPerson, RemovePerson


class Query(ObjectType):
health = String()

@staticmethod
def resolve_health(root, info):
return f"OK"

ticket = Field(Ticket, name=String())

@staticmethod
def resolve_ticket(root, info, name):
return Ticket(
name="SomeName",
description="...",
story_points=3
)

person = Field(Person, uid=String())

@staticmethod
def resolve_person(root, info, uid):
return controller.get_by_id(uid)

people = List(Person, limit=Int(), offset=Int())

@staticmethod
def resolve_people(root, info, limit: int = 10, offset: int = 0):
return controller.get_all(limit, offset)


class Mutations(ObjectType):
create_ticket = CreateTicket.Field(
name="create_ticket",
description="It creates a new Ticket...")

add_person = AddPerson.Field(
name="add_person",
description="It adds a person to the system...")

remove_person = RemovePerson.Field(
name="remove_person",
description="It remove the person from the database...")


schema = Schema(query=Query, mutation=Mutations)

Performance Comparison

FastAPI                 Flask                   gRPC
2.5365726947784424 3.588320016860962 0.6878361701965332
2.4663445949554443 3.4863481521606445 0.7431104183197021
2.8214125633239746 3.522181749343872 0.719865083694458
# test.py
# -*- coding: utf-8 -*-

import json
from time import time

import urllib3

http = urllib3.PoolManager()


def main():
query = {
"query": """mutation {
create_ticket (name: \"T-0001\", description: \"...\", storyPoints: 5) {
expectedDateline
}
}"""
}

start = time()

for _ in range(2000):
http.request(
"POST",
"http://localhost:3001/graphql",
headers={"Content-Type": "application/json"},
body=json.dumps(query)
)

print(time() - start)


if __name__ == "__main__":
main()
6.15591287612915
6.1439385414123535
5.966768741607666

Conclusions

--

--

--

Cloud & Solutions & Data Architect | Python Developer | Serverless Advocate

Love podcasts or audiobooks? Learn on the go with our new app.

Recommended from Medium

Introducing Speech Utility

Mastering Python: Step By Step Guide

Switchable graphics Nvidia / Intel in Linux

Android Tutorial: Building and Securing Your First App (Part 2)

Simpler production with containers

Three tips for entry level programmers from a junior

How To “Go Back” To A Previous Commit In BitBucket/GitHub Repository

Algorithm Interview Questions 3

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Alejandro Cora González

Alejandro Cora González

Cloud & Solutions & Data Architect | Python Developer | Serverless Advocate

More from Medium

Auto-scaling Shiny Apps in Multiple Regions with Fly.io

Getting started with Flask

NSQ with Docker in baby steps -70 lines of code

Automate Your Flask Deployment to VPS Using Github Actions