SlideShare une entreprise Scribd logo
1  sur  66
Télécharger pour lire hors ligne
Introduction to GraphQL
Johnson Liang
Agenda
● Fundamental parts of a GraphQL server
● Defining API shape - GraphQL schema
● Resolving object fields
● Mutative APIs
● Making requests to a GraphQL server
● Solving N+1 query problem: dataloader
graphql.org
API shape;
GraphQL Schema
Query;
GraphQL
Result
Fundamental parts
of a GraphQL server
Runnable GraphQL server code
const {
graphql, GraphQLSchema, GraphQLObjectType,
GraphQLString,
} = require('graphql');
const schema = new GraphQLSchema({
query: new GraphQLObjectType({
name: 'Query',
fields: {
serverTime: {
type: GraphQLString,
resolve() {
return (new Date).toLocaleString();
},
},
},
})
});
const query = `query { serverTime }`;
graphql(schema, query).then(result =>{
console.log(result.data);
});
// Prints:
// {serverTime: "9/5/2016, 6:28:46 PM"}
import graphene
from datetime import datetime
class Query(graphene.ObjectType):
server_time = graphene.Field(graphene.String)
def resolve_server_time(obj, args, context, info):
return str(datetime.now())
schema = graphene.Schema(query=Query)
query = 'query { serverTime }'
result = schema.execute(query)
print(result.data)
#: Prints: OrderedDict([('serverTime', '2017-07-24
19:10:38.127089')])
Defining API shape (GraphQL schema)
const {
graphql, GraphQLSchema, GraphQLObjectType,
GraphQLString,
} = require('graphql');
const schema = new GraphQLSchema({
query: new GraphQLObjectType({
name: 'Query',
fields: {
serverTime: {
type: GraphQLString,
resolve() {
return (new Date).toLocaleString();
},
},
},
})
});
const query = `query { serverTime }`;
graphql(schema, query).then(result =>{
console.log(result.data);
});
// Prints:
// {serverTime: "9/5/2016, 6:28:46 PM"}
import graphene
from datetime import datetime
class Query(graphene.ObjectType):
server_time = graphene.Field(graphene.String)
def resolve_server_time(obj, args, context, info):
return str(datetime.now())
schema = graphene.Schema(query=Query)
query = 'query { serverTime }'
result = schema.execute(query)
print(result.data)
#: Prints: OrderedDict([('serverTime', '2017-07-24
19:10:38.127089')])
Query in GraphQL
const {
graphql, GraphQLSchema, GraphQLObjectType,
GraphQLString,
} = require('graphql');
const schema = new GraphQLSchema({
query: new GraphQLObjectType({
name: 'Query',
fields: {
serverTime: {
type: GraphQLString,
resolve() {
return (new Date).toLocaleString();
},
},
},
})
});
const query = `query { serverTime }`;
graphql(schema, query).then(result =>{
console.log(result.data);
});
// Prints:
// {serverTime: "9/5/2016, 6:28:46 PM"}
import graphene
from datetime import datetime
class Query(graphene.ObjectType):
server_time = graphene.Field(graphene.String)
def resolve_server_time(obj, args, context, info):
return str(datetime.now())
schema = graphene.Schema(query=Query)
query = 'query { serverTime }'
result = schema.execute(query)
print(result.data)
#: Prints: OrderedDict([('serverTime', '2017-07-24
19:10:38.127089')])
Query Execution (query + schema → result)
const {
graphql, GraphQLSchema, GraphQLObjectType,
GraphQLString,
} = require('graphql');
const schema = new GraphQLSchema({
query: new GraphQLObjectType({
name: 'Query',
fields: {
serverTime: {
type: GraphQLString,
resolve() {
return (new Date).toLocaleString();
},
},
},
})
});
const query = `query { serverTime }`;
graphql(schema, query).then(result =>{
console.log(result.data);
});
// Prints:
// {serverTime: "9/5/2016, 6:28:46 PM"}
import graphene
from datetime import datetime
class Query(graphene.ObjectType):
server_time = graphene.Field(graphene.String)
def resolve_server_time(obj, args, context, info):
return str(datetime.now())
schema = graphene.Schema(query=Query)
query = 'query { serverTime }'
result = schema.execute(query)
print(result.data)
#: Prints: OrderedDict([('serverTime', '2017-07-24
19:10:38.127089')])
const {
graphql, GraphQLSchema, GraphQLObjectType,
GraphQLString,
} = require('graphql');
const schema = new GraphQLSchema({
query: new GraphQLObjectType({
name: 'Query',
fields: {
serverTime: {
type: GraphQLString,
resolve() {
return (new Date).toLocaleString();
},
},
},
})
});
const query = `query { serverTime }`;
graphql(schema, query).then(result =>{
console.log(result.data);
});
// Prints:
// {serverTime: "9/5/2016, 6:28:46 PM"}
Result
import graphene
from datetime import datetime
class Query(graphene.ObjectType):
server_time = graphene.Field(graphene.String)
def resolve_server_time(obj, args, context, info):
return str(datetime.now())
schema = graphene.Schema(query=Query)
query = 'query { serverTime }'
result = schema.execute(query)
print(result.data)
#: Prints: OrderedDict([('serverTime', '2017-07-24
19:10:38.127089')])
Parts in GraphQL server
JS: graphql(schema, query)
python: schema.execute(query)
query { serverTime }
Query
{ data: {
serverTime: "..."
} }
Schema
const {
graphql, GraphQLSchema, GraphQLObjectType,
GraphQLString,
} = require('graphql');
const schema = new GraphQLSchema({
query: new GraphQLObjectType({
name: 'Query',
fields: {
serverTime: {
type: GraphQLString,
resolve() {
return (new Date).toLocaleString();
},
},
},
})
});
const query = `query { serverTime }`;
graphql(schema, query).then(result =>{
console.log(result.data);
});
import graphene
from datetime import datetime
class Query(graphene.ObjectType):
server_time = graphene.Field(graphene.String)
def resolve_server_time(obj, args, context, info):
return str(datetime.now())
schema = graphene.Schema(query=Query)
query = 'query { serverTime }'
result = schema.execute(query)
print(result.data)
GraphQL server over HTTP
GraphQL server over HTTP
const {
graphql, GraphQLSchema, GraphQLObjectType,
GraphQLString,
} = require('graphql');
const schema = new GraphQLSchema({
query: new GraphQLObjectType({
name: 'Query',
fields: {
serverTime: {
type: GraphQLString,
resolve() {
return (new Date).toLocaleString();
},
},
},
})
});
app.post('/graphql', (req, res) => {
graphql(schema, req.body).then(res.json);
});
import graphene
from datetime import datetime
class Query(graphene.ObjectType):
server_time = graphene.Field(graphene.String)
def resolve_server_time(obj, args, context, info):
return str(datetime.now())
schema = graphene.Schema(query=Query)
@app.route('/graphql')
def graphql():
result = schema.execute(request.body)
return json.dumps({
data: result.data,
errors: result.errors
})
Ready-made GraphQL Server library
NodeJS
● express-graphql
● Apollo Server (real-world example)
Python
● Flask-GraphQL
Running Example
Clone
https://github.com/appier/graphql-cookbook
and follow the README
GraphiQL
GraphQL Results & error handling
{
serverTime
}
{
"data": {
"serverTime":
"9/5/2016, 6:28:46 PM"
}
}
{
"data": {
"serverTime": null,
},
"errors": {
"message": "...",
"locations": [...]
}
}
{
"errors": [
{
"message": "...",
"locations": [...]
}
]
}
Defining API shape -
GraphQL schema
Query & Schema
const schema = new GraphQLSchema({
query: new GraphQLObjectType({
name: 'Query',
fields: {
serverTime: {
type: GraphQLString,
resolve() { /* ...*/ },
},
},
})
class Query(graphene.ObjectType):
server_time = graphene.Field(...)
def resolve_server_time(obj, args,
context, info):
# ...
schema = graphene.Schema(query=Query)
query {
serverTime
}
Object Type
const schema = new GraphQLSchema({
query: new GraphQLObjectType({
name: 'Query',
fields: {
serverTime: {
type: GraphQLString,
resolve() { /* ...*/ },
},
},
})
class Query(graphene.ObjectType):
server_time = graphene.Field(...)
def resolve_server_time(obj, args,
context, info):
# ...
schema = graphene.Schema(query=Query)
query {
serverTime
}
class Query(graphene.ObjectType):
server_time = graphene.Field(...)
def resolve_server_time(obj, args,
context, info):
# ...
schema = graphene.Schema(query=Query)
Fields
const schema = new GraphQLSchema({
query: new GraphQLObjectType({
name: 'Query',
fields: {
serverTime: {
type: GraphQLString,
resolve() { /* ...*/ },
},
},
})
query {
serverTime
}
Resolvers
const schema = new GraphQLSchema({
query: new GraphQLObjectType({
name: 'Query',
fields: {
serverTime: {
type: GraphQLString,
resolve() { /* ...*/ },
},
},
})
class Query(graphene.ObjectType):
server_time = graphene.Field(...)
def resolve_server_time(obj, args,
context, info):
# ...
schema = graphene.Schema(query=Query)
query {
serverTime
}
Types
● Object Type
○ have "Fields"
○ each field have type & resolve function
● Scalar types
○ No more "fields", should resolve to a value
○ Built-in: String, Int, Float
○ Custom: specify how to serialize / deserialize. (NodeJS / Python)
○ Enum (NodeJS / Python) / server uses value, client uses name (scalar coercion)
Types (2)
● Modifiers
○ Lists (NodeJS / Python)
○ Non-Null (NodeJS / Python)
● Interfaces
○ Defines fields what must be implemented in an object type
● Union Type
○ Resolves to a type at run time
const schema = new GraphQLSchema({
query: new GraphQLObjectType({
name: 'Query',
fields: {
serverTime: {
type: Time,
resolve() { /* ...*/ },
},
},
})
class Query(graphene.ObjectType):
server_time = graphene.Field(Time)
def resolve_server_time(obj, args,
context, info):
# ...
schema = graphene.Schema(query=Query)
Field with a Object Type
query {
serverTime { hour, minute, second }
}
class Time(graphene.ObjeceType):
hour = graphene.Int
minute = graphene.Int
second = graphene.Int
const Time = new GraphQLObjectType({
name: 'Time',
fields: {
hour: {type: GraphQLInt},
minute: {type: GraphQLInt},
second: {type: GraphQLInt},
}
});
Field with a Object Type
query {
serverTime { hour, minute, second }
}
Schema, query and output
query {
article(...) {...}
reply {
author {...}
}
}
const schema = new GraphQLSchema({
query: new GraphQLObjectType({
name: 'Query',
fields: {
article: {
type: ArticleType,
args: {...},
resolve() {...}
},
articles: {
type: new GraphQLList(ArticleType),
resolve() {...}
},
reply: {
type: new GraphQLObjectType({
name: 'ReplyType',
fields: {
author: {
type: userType,
resolve() {...}
},
...
}
}),
resolve() {...}
Real-world example (simplified from Cofacts API server)
{
data: {
article: {...},
reply: {
author: {...}
}
}
}
schema query input output
query
article
reply
author
Resolving object fields
Resolving a field
const schema = new GraphQLSchema({
query: new GraphQLObjectType({
name: 'Query',
fields: {
serverTime: {
type: Time,
resolve() { /* ...*/ },
},
},
})
});
class Query(graphene.ObjectType):
serverTime = graphene.Field(Time)
def resolve_server_time(obj, args,
context, info):
# ...
schema = graphene.Schema(query=Query)
Resolver function signature
resolve(obj, args, context)
obj
● obj: The previous object, which for a field on the root Query type is often
not used.
(Text from official documentation)
resolve(obj, args, context)
obj
class Time(graphene.ObjectType):
hour = graphene.Int()
minute = graphene.Int()
second = graphene.Int()
def resolve_hour(obj, args, context, info):
return obj.hour
def resolve_minute(obj, args, context, info):
return obj.minute
def resolve_second(obj, args, context, info):
return obj.second
class Query(graphene.ObjectType):
server_time = graphene.Field(Time)
def resolve_server_time(obj, args, context, info):
return datetime.now()
schema = graphene.Schema(query=Query)
const Time = new GraphQLObjectType({
name: "Time",
fields: {
hour: {
type: GraphQLInt,
resolve: obj => obj.getHours() },
minute: {
type: GraphQLInt,
resolve: obj => obj.getMinutes() },
second: {
type: GraphQLInt,
resolve: obj => obj.getSeconds() }, }
});
const schema = new GraphQLSchema({
query: new GraphQLObjectType({
name: "Query",
fields: {
serverTime: {
type: Time,
resolve(){ return new Date; }
}
}
})
});
How resolver functions are invoked
Trivial resolvers
someField(obj) {
return obj.someField;
}
class Time(graphene.ObjectType):
hour = graphene.Int()
minute = graphene.Int()
second = graphene.Int()
class Query(graphene.ObjectType):
server_time = graphene.Field(Time)
def resolve_server_time(obj, args, context, info):
return datetime.now()
schema = graphene.Schema(query=Query)
const Time = new GraphQLObjectType({
name: "Time",
fields: {
hour: {type: GraphQLInt},
minute: {type: GraphQLInt},
second: {type: GraphQLInt}});
const schema = new GraphQLSchema({
query: new GraphQLObjectType({
name: "Query",
fields: {
serverTime: {
type: Time,
resolve(){
const date = new Date;
return {
hour: date.getHours(),
minute: date.getMinutes(),
second: date.getSeconds(),
};
}
}
resolve_some_field(obj):
return obj.some_field
has property hour, minute, second
Root value
const schema = new GraphQLSchema({
query: new GraphQLObjectType({
name: "Query",
fields: {
serverTime: {
type: Time,
resolve(obj){ ... }
}
}
})
});
graphql(schema, query, rootValue)
class Query(graphene.ObjectType):
server_time = graphene.Field(Time)
def resolve_server_time(obj):
return ...
schema = graphene.Schema(query=Query)
schema.execute(
query,
root_value=root_value
)
args
● obj: The previous object, which for a field on the root Query type is often
not used.
● args: The arguments provided to the field in the GraphQL query.
(Text from official documentation)
resolve(obj, args, context)
Arguments when querying a field
const schema = new GraphQLSchema({
query: new GraphQLObjectType({
name: 'Query',
fields: {
serverTime: {
type: GraphQLString,
args: {
timezone: {
type: GraphQLString,
description: 'Timezone name (Area/City)',
},
},
resolve(obj, { timezone = 'Asia/Taipei' }) {
return (new Date()).toLocaleString({
timeZone: timezone,
});;
},
},
},
}),
});
class Query(graphene.ObjectType):
server_time = graphene.Field(
graphene.String,
timezone=graphene.Argument(
graphene.Int,
default_value=8,
description="UTC+N, N=-24~24."
)
)
def resolve_server_time(obj, args, context, info):
tz = timezone(timedelta(hours=args['timezone']))
return str(datetime.now(tz))
schema = graphene.Schema(query=Query)
query {
serverTime(timezone: "UTC") {hour}
}
query {
serverTime(timezone: 0) {hour}
}
Args and Input Object Type
● Input Object types are Object types without arguments & resolvers
● Example (NodeJS, Python)
● Extended reading: filters, sorting, pagination, ...
query {
serverTime ( timezone: "UTC", offset: { hour: 3, minutes: 30 } ) {
hour
}
}
context
● obj: The previous object, which for a field on the root Query type is often
not used.
● args: The arguments provided to the field in the GraphQL query.
● context: A value which is provided to every resolver and holds important
contextual information like the currently logged in user, or access to a
database.
(Text from official documentation)
resolve(obj, args, context)
Context
#: Time object's hour resolver
def resolve_hour(obj, args, context, info):
return ...
#: Root Qyery type's serverTime resolver
def resolve_server_time(
obj, args, context, info
):
return ...
#: Execute the query
schema = graphene.Schema(
query=Query, context=context)
// Time object type's field
hour: {
type: GraphQLInt,
resolve(obj, args, context) {...}
}
// Root Query type's field
serverTime: {
type: Time,
resolve(obj, args, context) {...},
}
// Executing query
graphql(
schema, query, rootValue, context
)
Mutative APIs
query {
createUser(name:"John Doe"){
id
}
}
query {
user(name:"John Doe"){ id }
article(id:123) { text }
}
mutation {
createUser(...) {...}
createArticle(...) {...}
}
Run in parallel
Run in series
Mutations
class CreatePerson(graphene.Mutation):
class Input:
name = graphene.Argument(graphene.String)
ok = graphene.Field(graphene.Boolean)
person = graphene.Field(lambda: Person)
@staticmethod
def mutate(root, args, context, info):
#: Should do something that persists
#: the new Person here
person = Person(name=args.get('name'))
ok = True
return CreatePerson(person=person, ok=ok)
class Mutation(graphene.ObjectType):
create_person = CreatePerson.Field()
schema = graphene.Schema(
query=..., mutation=Mutation
)
const CreatePersonResult = new GraphQLObjectType({
name: 'CreatePersonResult', fields: {
ok: { type: GraphQLBoolean },
person: { type: Person },
}
})
const schema = new GraphQLSchema({
query: ...,
mutation: new GraphQLObjectType({
name: 'Mutation',
fields: {
CreatePerson: {
type: CreatePersonResult,
args: { name: { type: GraphQLString } },
resolve(obj, args) {
const person = {name: args.name};
// Should do something that persists
// the person
return { ok: true, person };
}
}
}
})
})
Mutations
class CreatePerson(graphene.Mutation):
class Input:
name = graphene.Argument(graphene.String)
ok = graphene.Field(graphene.Boolean)
person = graphene.Field(lambda: Person)
@staticmethod
def mutate(root, args, context, info):
#: Should do something that persists
#: the new Person here
person = Person(name=args.get('name'))
ok = True
return CreatePerson(person=person, ok=ok)
class Mutation(graphene.ObjectType):
create_person = CreatePerson.Field()
schema = graphene.Schema(
query=..., mutation=Mutation
)
const CreatePersonResult = new GraphQLObjectType({
name: 'CreatePersonResult', fields: {
ok: { type: GraphQLBoolean },
person: { type: Person },
}
})
const schema = new GraphQLSchema({
query: ...,
mutation: new GraphQLObjectType({
name: 'Mutation',
fields: {
CreatePerson: {
type: CreatePersonResult,
args: { name: { type: GraphQLString } },
resolve(obj, args) {
const person = {name: args.name};
// Should do something that persists
// the person
return { ok: true, person };
}
}
}
})
})
Mutations
class CreatePerson(graphene.Mutation):
class Input:
name = graphene.Argument(graphene.String)
ok = graphene.Field(graphene.Boolean)
person = graphene.Field(lambda: Person)
@staticmethod
def mutate(root, args, context, info):
#: Should do something that persists
#: the new Person here
person = Person(name=args.get('name'))
ok = True
return CreatePerson(person=person, ok=ok)
class Mutation(graphene.ObjectType):
create_person = CreatePerson.Field()
schema = graphene.Schema(
query=..., mutation=Mutation
)
const CreatePersonResult = new GraphQLObjectType({
name: 'CreatePersonResult', fields: {
ok: { type: GraphQLBoolean },
person: { type: Person },
}
})
const schema = new GraphQLSchema({
query: ...,
mutation: new GraphQLObjectType({
name: 'Mutation',
fields: {
CreatePerson: {
type: CreatePersonResult,
args: { name: { type: GraphQLString } },
resolve(obj, args) {
const person = {name: args.name};
// Should do something that persists
// the person
return { ok: true, person };
}
}
}
})
})
Extended reading -- Designing GraphQL Mutations
Making requests to GraphQL
Servers
Talk to GraphQL server via HTTP
● Depends on server (apollo-server / Flask-GraphQL) implementation
● Inspect graphiql network requests
● Mostly supports:
POST /graphql HTTP/1.1
Content-Type: application/json
{
"query": "...GraphQL Query string...",
}
Working with GraphQL Variables (Text from official documentation)
1
2
3
1. Replace the static value in the query with $variableName
2. Declare $variableName as one of the variables accepted by the query
3. Pass variableName: value in the separate, transport-specific (usually
JSON) variables dictionary
Working with GraphQL Variables (Text from official documentation)
POST /graphql HTTP/1.1
Content-Type: application/json
{
"query":
"query($offset:TimeInput){serverTimeWithInput(offset:$offset)}",
"variables": {
"offset": { "hour": 3 }
}
}
variables strings
1. Replace the static value in the query with $variableName
2. Declare $variableName as one of the variables accepted by the query
3. Pass variableName: value in the separate, transport-specific (usually
JSON) variables dictionary
12
3
Clients
● graphiql
● curl
● XMLHttpRequest / fetch
○ Example
● Client library - apollo-client
○ HOC (can be configured to use custom redux store)
○ Store normalization
○ Instance-level caching
○ Pre-fetching
Advanced query techniques
● Field alias - rename object key in result
● Fragments - to dedup fields
● Inline Fragments - for union types
● Directives - selectively query for fields
Extended reading: The Anatomy of GraphQL queries
Solving N+1 Problem:
Dataloader
How resolver functions are invoked
N+1 problem
{
users {
bestFriend {
displayName
}
}
}
1. Resolver for users field queries database,
returns a list of N users -- 1 query
2. For each user, resolver for bestFriend
field is fired
3. For each call to bestFriend's resolver, it
queries database with bestFriendId to
get the user document -- N queries
Tackling N+1 problem (1)
{
users {
bestFriend {
displayName
}
}
}
Solution 1: Resolve bestFriend in users' resolver
● Couples different resolving logic
● Need to know if "bestFriend" is queried
● Not recommended
Tackling N+1 problem (2)
{
users {
bestFriend {
displayName
}
}
}
Solution 2: Query all bestFriend in a batch
● After users' resolver return, bestFriend's
resolver is fired N times synchronously.
● Collect all bestFriendIds and query the
database at once
● With the help of dataloader
Dataloader - Batching & caching utility
Define a batch
function
Call load()
whenever you
want to
Get data you need
import DataLoader from 'dataloader';
const userLoader = new DataLoader(
ids => {
console.log('Invoked with', ids);
return User.getAllByIds(ids);
}
);
userLoader.load(1).then(console.log)
userLoader.load(2).then(console.log)
userLoader.load(3).then(console.log)
userLoader.load(1).then(console.log)
// Outputs:
// Invoked with [1,2,3]
// {id: 1, ...}
// {id: 2, ...}
// {id: 3, ...}
// {id: 1, ...}
from aiodataloader import DataLoader
class UserLoader(DataLoader):
async def batch_load_fn(self, ids):
print('Invoked with %s' % ids)
return User.get_all_by_ids(ids)
user_loader = UserLoader()
future1 = user_loader.load(1)
future2 = user_loader.load(2)
future3 = user_loader.load(3)
future4 = user_loader.load(1) # == future1
# prints:
# Invoked with [1, 2, 3]
print(await future1) # {id: 1, ...}
print(await future2) # {id: 2, ...}
print(await future3) # {id: 3, ...}
print(await future4) # {id: 1, ...}
Batch function
class UserLoader(DataLoader):
async def batch_load_fn(self, ids):
"""Fake data loading"""
return [{'id': id} for id in ids]
● Maps N IDs to N documents
● Input: IDs
○ Output are cached by ID
● Output: Promise<documents>
● Length of output must match input
○ If not found, return null
○ i-th ID in input should match i-th document
in output
const userLoader = new DataLoader(ids => {
// Fake data loading
return Promise.resolve(ids.map(id => ({id})))
})
dataloader instance methods
● load(id):
○ input: 1 ID
○ output: a promise that resolves to 1 loaded document
● loadMany(ids):
○ input: N IDs
○ output: a promise that resolves to N loaded documents
Multiple dataloader instances
● Prepare a batch function for each data-loading mechanism
● Create a dataloader instance for each batch function
● Batching & caching based on instances
user_loader = UserLoader()
articles_by_author_id_loader = ArticlesByAuthorIdLoader()
# Get user 1's best friends's articles
user = await user_loader.load(1)
print(await articles_by_author_id_loader.load(
user.best_friend_id
))
# prints:
# [article1, article2, ...]
const userLoader = new DataLoader(...)
const articlesByAuthorIdLoader = new DataLoader(...)
// Get user 1's best friends's articles
userLoader.load(1).then(
({bestFriendId}) =>
articlesByAuthorIdLoader.load(bestFriendId)
).then(console.log)
// prints:
// [article1, article2, ...]
# in root query type
user = graphene.Field(User)
def resolve_user(obj, args, context):
return context['user_loader'].load(args['id'])
# in user object type
best_friend_articles =
graphene.Field(graphene.List(Article))
def resolve_best_friend_articles(obj, args, context):
return context['articles_by_author_id_loader'].load(
obj['best_friend_id']
)
Combine dataloader with GraphQL schema
app.post('/graphql', bodyParser.json(),
graphqlExpress(req => ({
schema,
context: {
userLoader: new DataLoader(...),
articleByUserIdLoader: new DataLoader(...)
}
}))
)
// field "user" in root query type
{ type: User,
resolve(obj, {id}, {userLoader}) {
return userLoader.load(id);
} }
// field "bestFriendArticles" in User object type
{ type: new GraphQLList(Article),
resolve({bestFriendId}, args,
{articleByUserIdLoader}
) {
return articleByUserIdLoader.load(bestFriendId);
} }
class ViewWithContext(GraphQLView):
def get_context(self, request):
return {
'user_loader': UserLoader(),
'articles_by_author_id_loader':
ArticlesByAuthorIdLoader()
}
app.add_url_rule(
'/graphql', view_func=ViewWithContext.as_view(
'graphql', schema=schema, graphiql=True,
executor=AsyncioExecutor) )
Summary
● RESTful endpoints --> GraphQL
schema fields
● Strongly typed input / output
● Validation & Documentation
Other
Resources
● "Extended reading" in this slide
● GraphQL official doc
● How to GraphQL online course
● (JS only) generate schema from
GraphQL schema language
● Case studies
● FB group - GraphQL Taiwan

Contenu connexe

Tendances

Change Data Streaming Patterns For Microservices With Debezium (Gunnar Morlin...
Change Data Streaming Patterns For Microservices With Debezium (Gunnar Morlin...Change Data Streaming Patterns For Microservices With Debezium (Gunnar Morlin...
Change Data Streaming Patterns For Microservices With Debezium (Gunnar Morlin...
confluent
 

Tendances (20)

Building Modern APIs with GraphQL
Building Modern APIs with GraphQLBuilding Modern APIs with GraphQL
Building Modern APIs with GraphQL
 
REST vs GraphQL
REST vs GraphQLREST vs GraphQL
REST vs GraphQL
 
GraphQL: Enabling a new generation of API developer tools
GraphQL: Enabling a new generation of API developer toolsGraphQL: Enabling a new generation of API developer tools
GraphQL: Enabling a new generation of API developer tools
 
Introduction to graphQL
Introduction to graphQLIntroduction to graphQL
Introduction to graphQL
 
Introduction to GraphQL
Introduction to GraphQLIntroduction to GraphQL
Introduction to GraphQL
 
Graphql Intro (Tutorial and Example)
Graphql Intro (Tutorial and Example)Graphql Intro (Tutorial and Example)
Graphql Intro (Tutorial and Example)
 
Introduction to GraphQL
Introduction to GraphQLIntroduction to GraphQL
Introduction to GraphQL
 
Spring GraphQL
Spring GraphQLSpring GraphQL
Spring GraphQL
 
Graphql
GraphqlGraphql
Graphql
 
Getting Started with Spring for GraphQL
Getting Started with Spring for GraphQLGetting Started with Spring for GraphQL
Getting Started with Spring for GraphQL
 
GraphQL Fundamentals
GraphQL FundamentalsGraphQL Fundamentals
GraphQL Fundamentals
 
How to GraphQL
How to GraphQLHow to GraphQL
How to GraphQL
 
GraphQL Data Loaders - How to feed your GraphQL API with data the smart way
GraphQL Data Loaders - How to feed your GraphQL API with data the smart wayGraphQL Data Loaders - How to feed your GraphQL API with data the smart way
GraphQL Data Loaders - How to feed your GraphQL API with data the smart way
 
Change Data Streaming Patterns For Microservices With Debezium (Gunnar Morlin...
Change Data Streaming Patterns For Microservices With Debezium (Gunnar Morlin...Change Data Streaming Patterns For Microservices With Debezium (Gunnar Morlin...
Change Data Streaming Patterns For Microservices With Debezium (Gunnar Morlin...
 
Better APIs with GraphQL
Better APIs with GraphQL Better APIs with GraphQL
Better APIs with GraphQL
 
RabbitMQ & Kafka
RabbitMQ & KafkaRabbitMQ & Kafka
RabbitMQ & Kafka
 
Attacking GraphQL
Attacking GraphQLAttacking GraphQL
Attacking GraphQL
 
Introduction to GraphQL (or How I Learned to Stop Worrying about REST APIs)
Introduction to GraphQL (or How I Learned to Stop Worrying about REST APIs)Introduction to GraphQL (or How I Learned to Stop Worrying about REST APIs)
Introduction to GraphQL (or How I Learned to Stop Worrying about REST APIs)
 
Spring Cloud Config
Spring Cloud ConfigSpring Cloud Config
Spring Cloud Config
 
Kafka basics
Kafka basicsKafka basics
Kafka basics
 

Similaire à Introduction to GraphQL

Similaire à Introduction to GraphQL (20)

[2019-07] GraphQL in depth (serverside)
[2019-07] GraphQL in depth (serverside)[2019-07] GraphQL in depth (serverside)
[2019-07] GraphQL in depth (serverside)
 
Graphql, REST and Apollo
Graphql, REST and ApolloGraphql, REST and Apollo
Graphql, REST and Apollo
 
MongoDB World 2019: Building a GraphQL API with MongoDB, Prisma, & TypeScript
MongoDB World 2019: Building a GraphQL API with MongoDB, Prisma, & TypeScriptMongoDB World 2019: Building a GraphQL API with MongoDB, Prisma, & TypeScript
MongoDB World 2019: Building a GraphQL API with MongoDB, Prisma, & TypeScript
 
Taller: Datos en tiempo real con GraphQL
Taller: Datos en tiempo real con GraphQLTaller: Datos en tiempo real con GraphQL
Taller: Datos en tiempo real con GraphQL
 
GraphQL & Prisma from Scratch
GraphQL & Prisma from ScratchGraphQL & Prisma from Scratch
GraphQL & Prisma from Scratch
 
Managing GraphQL servers with AWS Fargate & Prisma Cloud
Managing GraphQL servers  with AWS Fargate & Prisma CloudManaging GraphQL servers  with AWS Fargate & Prisma Cloud
Managing GraphQL servers with AWS Fargate & Prisma Cloud
 
Intro to GraphQL on Android with Apollo DroidconNYC 2017
Intro to GraphQL on Android with Apollo DroidconNYC 2017Intro to GraphQL on Android with Apollo DroidconNYC 2017
Intro to GraphQL on Android with Apollo DroidconNYC 2017
 
Next-generation API Development with GraphQL and Prisma
Next-generation API Development with GraphQL and PrismaNext-generation API Development with GraphQL and Prisma
Next-generation API Development with GraphQL and Prisma
 
Tomer Elmalem - GraphQL APIs: REST in Peace - Codemotion Milan 2017
Tomer Elmalem - GraphQL APIs: REST in Peace - Codemotion Milan 2017Tomer Elmalem - GraphQL APIs: REST in Peace - Codemotion Milan 2017
Tomer Elmalem - GraphQL APIs: REST in Peace - Codemotion Milan 2017
 
GraphQL, Redux, and React
GraphQL, Redux, and ReactGraphQL, Redux, and React
GraphQL, Redux, and React
 
Nikita Galkin "Looking for the right tech stack for GraphQL application"
Nikita Galkin "Looking for the right tech stack for GraphQL application"Nikita Galkin "Looking for the right tech stack for GraphQL application"
Nikita Galkin "Looking for the right tech stack for GraphQL application"
 
GPerf Using Jesque
GPerf Using JesqueGPerf Using Jesque
GPerf Using Jesque
 
Marble Testing RxJS streams
Marble Testing RxJS streamsMarble Testing RxJS streams
Marble Testing RxJS streams
 
Developing web-apps like it's 2013
Developing web-apps like it's 2013Developing web-apps like it's 2013
Developing web-apps like it's 2013
 
MongoDB.local Berlin: Building a GraphQL API with MongoDB, Prisma and Typescript
MongoDB.local Berlin: Building a GraphQL API with MongoDB, Prisma and TypescriptMongoDB.local Berlin: Building a GraphQL API with MongoDB, Prisma and Typescript
MongoDB.local Berlin: Building a GraphQL API with MongoDB, Prisma and Typescript
 
GraphQL Bangkok Meetup 2.0
GraphQL Bangkok Meetup 2.0GraphQL Bangkok Meetup 2.0
GraphQL Bangkok Meetup 2.0
 
APIdays Paris 2018 - Building scalable, type-safe GraphQL servers from scratc...
APIdays Paris 2018 - Building scalable, type-safe GraphQL servers from scratc...APIdays Paris 2018 - Building scalable, type-safe GraphQL servers from scratc...
APIdays Paris 2018 - Building scalable, type-safe GraphQL servers from scratc...
 
Testowanie JavaScript
Testowanie JavaScriptTestowanie JavaScript
Testowanie JavaScript
 
Graphql with Flamingo
Graphql with FlamingoGraphql with Flamingo
Graphql with Flamingo
 
Extend GraphQL with directives
Extend GraphQL with directivesExtend GraphQL with directives
Extend GraphQL with directives
 

Dernier

Cloud Frontiers: A Deep Dive into Serverless Spatial Data and FME
Cloud Frontiers:  A Deep Dive into Serverless Spatial Data and FMECloud Frontiers:  A Deep Dive into Serverless Spatial Data and FME
Cloud Frontiers: A Deep Dive into Serverless Spatial Data and FME
Safe Software
 
Why Teams call analytics are critical to your entire business
Why Teams call analytics are critical to your entire businessWhy Teams call analytics are critical to your entire business
Why Teams call analytics are critical to your entire business
panagenda
 

Dernier (20)

Cloud Frontiers: A Deep Dive into Serverless Spatial Data and FME
Cloud Frontiers:  A Deep Dive into Serverless Spatial Data and FMECloud Frontiers:  A Deep Dive into Serverless Spatial Data and FME
Cloud Frontiers: A Deep Dive into Serverless Spatial Data and FME
 
Apidays Singapore 2024 - Modernizing Securities Finance by Madhu Subbu
Apidays Singapore 2024 - Modernizing Securities Finance by Madhu SubbuApidays Singapore 2024 - Modernizing Securities Finance by Madhu Subbu
Apidays Singapore 2024 - Modernizing Securities Finance by Madhu Subbu
 
Apidays Singapore 2024 - Building Digital Trust in a Digital Economy by Veron...
Apidays Singapore 2024 - Building Digital Trust in a Digital Economy by Veron...Apidays Singapore 2024 - Building Digital Trust in a Digital Economy by Veron...
Apidays Singapore 2024 - Building Digital Trust in a Digital Economy by Veron...
 
How to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected WorkerHow to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected Worker
 
MS Copilot expands with MS Graph connectors
MS Copilot expands with MS Graph connectorsMS Copilot expands with MS Graph connectors
MS Copilot expands with MS Graph connectors
 
A Beginners Guide to Building a RAG App Using Open Source Milvus
A Beginners Guide to Building a RAG App Using Open Source MilvusA Beginners Guide to Building a RAG App Using Open Source Milvus
A Beginners Guide to Building a RAG App Using Open Source Milvus
 
Axa Assurance Maroc - Insurer Innovation Award 2024
Axa Assurance Maroc - Insurer Innovation Award 2024Axa Assurance Maroc - Insurer Innovation Award 2024
Axa Assurance Maroc - Insurer Innovation Award 2024
 
Data Cloud, More than a CDP by Matt Robison
Data Cloud, More than a CDP by Matt RobisonData Cloud, More than a CDP by Matt Robison
Data Cloud, More than a CDP by Matt Robison
 
Polkadot JAM Slides - Token2049 - By Dr. Gavin Wood
Polkadot JAM Slides - Token2049 - By Dr. Gavin WoodPolkadot JAM Slides - Token2049 - By Dr. Gavin Wood
Polkadot JAM Slides - Token2049 - By Dr. Gavin Wood
 
Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...
Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...
Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...
 
Why Teams call analytics are critical to your entire business
Why Teams call analytics are critical to your entire businessWhy Teams call analytics are critical to your entire business
Why Teams call analytics are critical to your entire business
 
TrustArc Webinar - Unlock the Power of AI-Driven Data Discovery
TrustArc Webinar - Unlock the Power of AI-Driven Data DiscoveryTrustArc Webinar - Unlock the Power of AI-Driven Data Discovery
TrustArc Webinar - Unlock the Power of AI-Driven Data Discovery
 
Apidays New York 2024 - Accelerating FinTech Innovation by Vasa Krishnan, Fin...
Apidays New York 2024 - Accelerating FinTech Innovation by Vasa Krishnan, Fin...Apidays New York 2024 - Accelerating FinTech Innovation by Vasa Krishnan, Fin...
Apidays New York 2024 - Accelerating FinTech Innovation by Vasa Krishnan, Fin...
 
Repurposing LNG terminals for Hydrogen Ammonia: Feasibility and Cost Saving
Repurposing LNG terminals for Hydrogen Ammonia: Feasibility and Cost SavingRepurposing LNG terminals for Hydrogen Ammonia: Feasibility and Cost Saving
Repurposing LNG terminals for Hydrogen Ammonia: Feasibility and Cost Saving
 
Powerful Google developer tools for immediate impact! (2023-24 C)
Powerful Google developer tools for immediate impact! (2023-24 C)Powerful Google developer tools for immediate impact! (2023-24 C)
Powerful Google developer tools for immediate impact! (2023-24 C)
 
Web Form Automation for Bonterra Impact Management (fka Social Solutions Apri...
Web Form Automation for Bonterra Impact Management (fka Social Solutions Apri...Web Form Automation for Bonterra Impact Management (fka Social Solutions Apri...
Web Form Automation for Bonterra Impact Management (fka Social Solutions Apri...
 
GenAI Risks & Security Meetup 01052024.pdf
GenAI Risks & Security Meetup 01052024.pdfGenAI Risks & Security Meetup 01052024.pdf
GenAI Risks & Security Meetup 01052024.pdf
 
Manulife - Insurer Transformation Award 2024
Manulife - Insurer Transformation Award 2024Manulife - Insurer Transformation Award 2024
Manulife - Insurer Transformation Award 2024
 
EMPOWERMENT TECHNOLOGY GRADE 11 QUARTER 2 REVIEWER
EMPOWERMENT TECHNOLOGY GRADE 11 QUARTER 2 REVIEWEREMPOWERMENT TECHNOLOGY GRADE 11 QUARTER 2 REVIEWER
EMPOWERMENT TECHNOLOGY GRADE 11 QUARTER 2 REVIEWER
 
Ransomware_Q4_2023. The report. [EN].pdf
Ransomware_Q4_2023. The report. [EN].pdfRansomware_Q4_2023. The report. [EN].pdf
Ransomware_Q4_2023. The report. [EN].pdf
 

Introduction to GraphQL

  • 2. Agenda ● Fundamental parts of a GraphQL server ● Defining API shape - GraphQL schema ● Resolving object fields ● Mutative APIs ● Making requests to a GraphQL server ● Solving N+1 query problem: dataloader
  • 5. Fundamental parts of a GraphQL server
  • 6. Runnable GraphQL server code const { graphql, GraphQLSchema, GraphQLObjectType, GraphQLString, } = require('graphql'); const schema = new GraphQLSchema({ query: new GraphQLObjectType({ name: 'Query', fields: { serverTime: { type: GraphQLString, resolve() { return (new Date).toLocaleString(); }, }, }, }) }); const query = `query { serverTime }`; graphql(schema, query).then(result =>{ console.log(result.data); }); // Prints: // {serverTime: "9/5/2016, 6:28:46 PM"} import graphene from datetime import datetime class Query(graphene.ObjectType): server_time = graphene.Field(graphene.String) def resolve_server_time(obj, args, context, info): return str(datetime.now()) schema = graphene.Schema(query=Query) query = 'query { serverTime }' result = schema.execute(query) print(result.data) #: Prints: OrderedDict([('serverTime', '2017-07-24 19:10:38.127089')])
  • 7. Defining API shape (GraphQL schema) const { graphql, GraphQLSchema, GraphQLObjectType, GraphQLString, } = require('graphql'); const schema = new GraphQLSchema({ query: new GraphQLObjectType({ name: 'Query', fields: { serverTime: { type: GraphQLString, resolve() { return (new Date).toLocaleString(); }, }, }, }) }); const query = `query { serverTime }`; graphql(schema, query).then(result =>{ console.log(result.data); }); // Prints: // {serverTime: "9/5/2016, 6:28:46 PM"} import graphene from datetime import datetime class Query(graphene.ObjectType): server_time = graphene.Field(graphene.String) def resolve_server_time(obj, args, context, info): return str(datetime.now()) schema = graphene.Schema(query=Query) query = 'query { serverTime }' result = schema.execute(query) print(result.data) #: Prints: OrderedDict([('serverTime', '2017-07-24 19:10:38.127089')])
  • 8. Query in GraphQL const { graphql, GraphQLSchema, GraphQLObjectType, GraphQLString, } = require('graphql'); const schema = new GraphQLSchema({ query: new GraphQLObjectType({ name: 'Query', fields: { serverTime: { type: GraphQLString, resolve() { return (new Date).toLocaleString(); }, }, }, }) }); const query = `query { serverTime }`; graphql(schema, query).then(result =>{ console.log(result.data); }); // Prints: // {serverTime: "9/5/2016, 6:28:46 PM"} import graphene from datetime import datetime class Query(graphene.ObjectType): server_time = graphene.Field(graphene.String) def resolve_server_time(obj, args, context, info): return str(datetime.now()) schema = graphene.Schema(query=Query) query = 'query { serverTime }' result = schema.execute(query) print(result.data) #: Prints: OrderedDict([('serverTime', '2017-07-24 19:10:38.127089')])
  • 9. Query Execution (query + schema → result) const { graphql, GraphQLSchema, GraphQLObjectType, GraphQLString, } = require('graphql'); const schema = new GraphQLSchema({ query: new GraphQLObjectType({ name: 'Query', fields: { serverTime: { type: GraphQLString, resolve() { return (new Date).toLocaleString(); }, }, }, }) }); const query = `query { serverTime }`; graphql(schema, query).then(result =>{ console.log(result.data); }); // Prints: // {serverTime: "9/5/2016, 6:28:46 PM"} import graphene from datetime import datetime class Query(graphene.ObjectType): server_time = graphene.Field(graphene.String) def resolve_server_time(obj, args, context, info): return str(datetime.now()) schema = graphene.Schema(query=Query) query = 'query { serverTime }' result = schema.execute(query) print(result.data) #: Prints: OrderedDict([('serverTime', '2017-07-24 19:10:38.127089')])
  • 10. const { graphql, GraphQLSchema, GraphQLObjectType, GraphQLString, } = require('graphql'); const schema = new GraphQLSchema({ query: new GraphQLObjectType({ name: 'Query', fields: { serverTime: { type: GraphQLString, resolve() { return (new Date).toLocaleString(); }, }, }, }) }); const query = `query { serverTime }`; graphql(schema, query).then(result =>{ console.log(result.data); }); // Prints: // {serverTime: "9/5/2016, 6:28:46 PM"} Result import graphene from datetime import datetime class Query(graphene.ObjectType): server_time = graphene.Field(graphene.String) def resolve_server_time(obj, args, context, info): return str(datetime.now()) schema = graphene.Schema(query=Query) query = 'query { serverTime }' result = schema.execute(query) print(result.data) #: Prints: OrderedDict([('serverTime', '2017-07-24 19:10:38.127089')])
  • 11. Parts in GraphQL server JS: graphql(schema, query) python: schema.execute(query) query { serverTime } Query { data: { serverTime: "..." } } Schema
  • 12. const { graphql, GraphQLSchema, GraphQLObjectType, GraphQLString, } = require('graphql'); const schema = new GraphQLSchema({ query: new GraphQLObjectType({ name: 'Query', fields: { serverTime: { type: GraphQLString, resolve() { return (new Date).toLocaleString(); }, }, }, }) }); const query = `query { serverTime }`; graphql(schema, query).then(result =>{ console.log(result.data); }); import graphene from datetime import datetime class Query(graphene.ObjectType): server_time = graphene.Field(graphene.String) def resolve_server_time(obj, args, context, info): return str(datetime.now()) schema = graphene.Schema(query=Query) query = 'query { serverTime }' result = schema.execute(query) print(result.data) GraphQL server over HTTP
  • 13. GraphQL server over HTTP const { graphql, GraphQLSchema, GraphQLObjectType, GraphQLString, } = require('graphql'); const schema = new GraphQLSchema({ query: new GraphQLObjectType({ name: 'Query', fields: { serverTime: { type: GraphQLString, resolve() { return (new Date).toLocaleString(); }, }, }, }) }); app.post('/graphql', (req, res) => { graphql(schema, req.body).then(res.json); }); import graphene from datetime import datetime class Query(graphene.ObjectType): server_time = graphene.Field(graphene.String) def resolve_server_time(obj, args, context, info): return str(datetime.now()) schema = graphene.Schema(query=Query) @app.route('/graphql') def graphql(): result = schema.execute(request.body) return json.dumps({ data: result.data, errors: result.errors })
  • 14. Ready-made GraphQL Server library NodeJS ● express-graphql ● Apollo Server (real-world example) Python ● Flask-GraphQL
  • 17. GraphQL Results & error handling { serverTime } { "data": { "serverTime": "9/5/2016, 6:28:46 PM" } } { "data": { "serverTime": null, }, "errors": { "message": "...", "locations": [...] } } { "errors": [ { "message": "...", "locations": [...] } ] }
  • 18. Defining API shape - GraphQL schema
  • 19. Query & Schema const schema = new GraphQLSchema({ query: new GraphQLObjectType({ name: 'Query', fields: { serverTime: { type: GraphQLString, resolve() { /* ...*/ }, }, }, }) class Query(graphene.ObjectType): server_time = graphene.Field(...) def resolve_server_time(obj, args, context, info): # ... schema = graphene.Schema(query=Query) query { serverTime }
  • 20. Object Type const schema = new GraphQLSchema({ query: new GraphQLObjectType({ name: 'Query', fields: { serverTime: { type: GraphQLString, resolve() { /* ...*/ }, }, }, }) class Query(graphene.ObjectType): server_time = graphene.Field(...) def resolve_server_time(obj, args, context, info): # ... schema = graphene.Schema(query=Query) query { serverTime }
  • 21. class Query(graphene.ObjectType): server_time = graphene.Field(...) def resolve_server_time(obj, args, context, info): # ... schema = graphene.Schema(query=Query) Fields const schema = new GraphQLSchema({ query: new GraphQLObjectType({ name: 'Query', fields: { serverTime: { type: GraphQLString, resolve() { /* ...*/ }, }, }, }) query { serverTime }
  • 22. Resolvers const schema = new GraphQLSchema({ query: new GraphQLObjectType({ name: 'Query', fields: { serverTime: { type: GraphQLString, resolve() { /* ...*/ }, }, }, }) class Query(graphene.ObjectType): server_time = graphene.Field(...) def resolve_server_time(obj, args, context, info): # ... schema = graphene.Schema(query=Query) query { serverTime }
  • 23. Types ● Object Type ○ have "Fields" ○ each field have type & resolve function ● Scalar types ○ No more "fields", should resolve to a value ○ Built-in: String, Int, Float ○ Custom: specify how to serialize / deserialize. (NodeJS / Python) ○ Enum (NodeJS / Python) / server uses value, client uses name (scalar coercion)
  • 24. Types (2) ● Modifiers ○ Lists (NodeJS / Python) ○ Non-Null (NodeJS / Python) ● Interfaces ○ Defines fields what must be implemented in an object type ● Union Type ○ Resolves to a type at run time
  • 25. const schema = new GraphQLSchema({ query: new GraphQLObjectType({ name: 'Query', fields: { serverTime: { type: Time, resolve() { /* ...*/ }, }, }, }) class Query(graphene.ObjectType): server_time = graphene.Field(Time) def resolve_server_time(obj, args, context, info): # ... schema = graphene.Schema(query=Query) Field with a Object Type query { serverTime { hour, minute, second } }
  • 26. class Time(graphene.ObjeceType): hour = graphene.Int minute = graphene.Int second = graphene.Int const Time = new GraphQLObjectType({ name: 'Time', fields: { hour: {type: GraphQLInt}, minute: {type: GraphQLInt}, second: {type: GraphQLInt}, } }); Field with a Object Type query { serverTime { hour, minute, second } }
  • 28. query { article(...) {...} reply { author {...} } } const schema = new GraphQLSchema({ query: new GraphQLObjectType({ name: 'Query', fields: { article: { type: ArticleType, args: {...}, resolve() {...} }, articles: { type: new GraphQLList(ArticleType), resolve() {...} }, reply: { type: new GraphQLObjectType({ name: 'ReplyType', fields: { author: { type: userType, resolve() {...} }, ... } }), resolve() {...} Real-world example (simplified from Cofacts API server) { data: { article: {...}, reply: { author: {...} } } } schema query input output query article reply author
  • 30. Resolving a field const schema = new GraphQLSchema({ query: new GraphQLObjectType({ name: 'Query', fields: { serverTime: { type: Time, resolve() { /* ...*/ }, }, }, }) }); class Query(graphene.ObjectType): serverTime = graphene.Field(Time) def resolve_server_time(obj, args, context, info): # ... schema = graphene.Schema(query=Query)
  • 32. obj ● obj: The previous object, which for a field on the root Query type is often not used. (Text from official documentation) resolve(obj, args, context)
  • 33. obj class Time(graphene.ObjectType): hour = graphene.Int() minute = graphene.Int() second = graphene.Int() def resolve_hour(obj, args, context, info): return obj.hour def resolve_minute(obj, args, context, info): return obj.minute def resolve_second(obj, args, context, info): return obj.second class Query(graphene.ObjectType): server_time = graphene.Field(Time) def resolve_server_time(obj, args, context, info): return datetime.now() schema = graphene.Schema(query=Query) const Time = new GraphQLObjectType({ name: "Time", fields: { hour: { type: GraphQLInt, resolve: obj => obj.getHours() }, minute: { type: GraphQLInt, resolve: obj => obj.getMinutes() }, second: { type: GraphQLInt, resolve: obj => obj.getSeconds() }, } }); const schema = new GraphQLSchema({ query: new GraphQLObjectType({ name: "Query", fields: { serverTime: { type: Time, resolve(){ return new Date; } } } }) });
  • 34. How resolver functions are invoked
  • 35. Trivial resolvers someField(obj) { return obj.someField; } class Time(graphene.ObjectType): hour = graphene.Int() minute = graphene.Int() second = graphene.Int() class Query(graphene.ObjectType): server_time = graphene.Field(Time) def resolve_server_time(obj, args, context, info): return datetime.now() schema = graphene.Schema(query=Query) const Time = new GraphQLObjectType({ name: "Time", fields: { hour: {type: GraphQLInt}, minute: {type: GraphQLInt}, second: {type: GraphQLInt}}); const schema = new GraphQLSchema({ query: new GraphQLObjectType({ name: "Query", fields: { serverTime: { type: Time, resolve(){ const date = new Date; return { hour: date.getHours(), minute: date.getMinutes(), second: date.getSeconds(), }; } } resolve_some_field(obj): return obj.some_field has property hour, minute, second
  • 36. Root value const schema = new GraphQLSchema({ query: new GraphQLObjectType({ name: "Query", fields: { serverTime: { type: Time, resolve(obj){ ... } } } }) }); graphql(schema, query, rootValue) class Query(graphene.ObjectType): server_time = graphene.Field(Time) def resolve_server_time(obj): return ... schema = graphene.Schema(query=Query) schema.execute( query, root_value=root_value )
  • 37. args ● obj: The previous object, which for a field on the root Query type is often not used. ● args: The arguments provided to the field in the GraphQL query. (Text from official documentation) resolve(obj, args, context)
  • 38. Arguments when querying a field const schema = new GraphQLSchema({ query: new GraphQLObjectType({ name: 'Query', fields: { serverTime: { type: GraphQLString, args: { timezone: { type: GraphQLString, description: 'Timezone name (Area/City)', }, }, resolve(obj, { timezone = 'Asia/Taipei' }) { return (new Date()).toLocaleString({ timeZone: timezone, });; }, }, }, }), }); class Query(graphene.ObjectType): server_time = graphene.Field( graphene.String, timezone=graphene.Argument( graphene.Int, default_value=8, description="UTC+N, N=-24~24." ) ) def resolve_server_time(obj, args, context, info): tz = timezone(timedelta(hours=args['timezone'])) return str(datetime.now(tz)) schema = graphene.Schema(query=Query) query { serverTime(timezone: "UTC") {hour} } query { serverTime(timezone: 0) {hour} }
  • 39. Args and Input Object Type ● Input Object types are Object types without arguments & resolvers ● Example (NodeJS, Python) ● Extended reading: filters, sorting, pagination, ... query { serverTime ( timezone: "UTC", offset: { hour: 3, minutes: 30 } ) { hour } }
  • 40. context ● obj: The previous object, which for a field on the root Query type is often not used. ● args: The arguments provided to the field in the GraphQL query. ● context: A value which is provided to every resolver and holds important contextual information like the currently logged in user, or access to a database. (Text from official documentation) resolve(obj, args, context)
  • 41. Context #: Time object's hour resolver def resolve_hour(obj, args, context, info): return ... #: Root Qyery type's serverTime resolver def resolve_server_time( obj, args, context, info ): return ... #: Execute the query schema = graphene.Schema( query=Query, context=context) // Time object type's field hour: { type: GraphQLInt, resolve(obj, args, context) {...} } // Root Query type's field serverTime: { type: Time, resolve(obj, args, context) {...}, } // Executing query graphql( schema, query, rootValue, context )
  • 44. query { user(name:"John Doe"){ id } article(id:123) { text } } mutation { createUser(...) {...} createArticle(...) {...} } Run in parallel Run in series
  • 45. Mutations class CreatePerson(graphene.Mutation): class Input: name = graphene.Argument(graphene.String) ok = graphene.Field(graphene.Boolean) person = graphene.Field(lambda: Person) @staticmethod def mutate(root, args, context, info): #: Should do something that persists #: the new Person here person = Person(name=args.get('name')) ok = True return CreatePerson(person=person, ok=ok) class Mutation(graphene.ObjectType): create_person = CreatePerson.Field() schema = graphene.Schema( query=..., mutation=Mutation ) const CreatePersonResult = new GraphQLObjectType({ name: 'CreatePersonResult', fields: { ok: { type: GraphQLBoolean }, person: { type: Person }, } }) const schema = new GraphQLSchema({ query: ..., mutation: new GraphQLObjectType({ name: 'Mutation', fields: { CreatePerson: { type: CreatePersonResult, args: { name: { type: GraphQLString } }, resolve(obj, args) { const person = {name: args.name}; // Should do something that persists // the person return { ok: true, person }; } } } }) })
  • 46. Mutations class CreatePerson(graphene.Mutation): class Input: name = graphene.Argument(graphene.String) ok = graphene.Field(graphene.Boolean) person = graphene.Field(lambda: Person) @staticmethod def mutate(root, args, context, info): #: Should do something that persists #: the new Person here person = Person(name=args.get('name')) ok = True return CreatePerson(person=person, ok=ok) class Mutation(graphene.ObjectType): create_person = CreatePerson.Field() schema = graphene.Schema( query=..., mutation=Mutation ) const CreatePersonResult = new GraphQLObjectType({ name: 'CreatePersonResult', fields: { ok: { type: GraphQLBoolean }, person: { type: Person }, } }) const schema = new GraphQLSchema({ query: ..., mutation: new GraphQLObjectType({ name: 'Mutation', fields: { CreatePerson: { type: CreatePersonResult, args: { name: { type: GraphQLString } }, resolve(obj, args) { const person = {name: args.name}; // Should do something that persists // the person return { ok: true, person }; } } } }) })
  • 47. Mutations class CreatePerson(graphene.Mutation): class Input: name = graphene.Argument(graphene.String) ok = graphene.Field(graphene.Boolean) person = graphene.Field(lambda: Person) @staticmethod def mutate(root, args, context, info): #: Should do something that persists #: the new Person here person = Person(name=args.get('name')) ok = True return CreatePerson(person=person, ok=ok) class Mutation(graphene.ObjectType): create_person = CreatePerson.Field() schema = graphene.Schema( query=..., mutation=Mutation ) const CreatePersonResult = new GraphQLObjectType({ name: 'CreatePersonResult', fields: { ok: { type: GraphQLBoolean }, person: { type: Person }, } }) const schema = new GraphQLSchema({ query: ..., mutation: new GraphQLObjectType({ name: 'Mutation', fields: { CreatePerson: { type: CreatePersonResult, args: { name: { type: GraphQLString } }, resolve(obj, args) { const person = {name: args.name}; // Should do something that persists // the person return { ok: true, person }; } } } }) })
  • 48. Extended reading -- Designing GraphQL Mutations
  • 49. Making requests to GraphQL Servers
  • 50. Talk to GraphQL server via HTTP ● Depends on server (apollo-server / Flask-GraphQL) implementation ● Inspect graphiql network requests ● Mostly supports: POST /graphql HTTP/1.1 Content-Type: application/json { "query": "...GraphQL Query string...", }
  • 51. Working with GraphQL Variables (Text from official documentation) 1 2 3 1. Replace the static value in the query with $variableName 2. Declare $variableName as one of the variables accepted by the query 3. Pass variableName: value in the separate, transport-specific (usually JSON) variables dictionary
  • 52. Working with GraphQL Variables (Text from official documentation) POST /graphql HTTP/1.1 Content-Type: application/json { "query": "query($offset:TimeInput){serverTimeWithInput(offset:$offset)}", "variables": { "offset": { "hour": 3 } } } variables strings 1. Replace the static value in the query with $variableName 2. Declare $variableName as one of the variables accepted by the query 3. Pass variableName: value in the separate, transport-specific (usually JSON) variables dictionary 12 3
  • 53. Clients ● graphiql ● curl ● XMLHttpRequest / fetch ○ Example ● Client library - apollo-client ○ HOC (can be configured to use custom redux store) ○ Store normalization ○ Instance-level caching ○ Pre-fetching
  • 54. Advanced query techniques ● Field alias - rename object key in result ● Fragments - to dedup fields ● Inline Fragments - for union types ● Directives - selectively query for fields Extended reading: The Anatomy of GraphQL queries
  • 56. How resolver functions are invoked
  • 57. N+1 problem { users { bestFriend { displayName } } } 1. Resolver for users field queries database, returns a list of N users -- 1 query 2. For each user, resolver for bestFriend field is fired 3. For each call to bestFriend's resolver, it queries database with bestFriendId to get the user document -- N queries
  • 58. Tackling N+1 problem (1) { users { bestFriend { displayName } } } Solution 1: Resolve bestFriend in users' resolver ● Couples different resolving logic ● Need to know if "bestFriend" is queried ● Not recommended
  • 59. Tackling N+1 problem (2) { users { bestFriend { displayName } } } Solution 2: Query all bestFriend in a batch ● After users' resolver return, bestFriend's resolver is fired N times synchronously. ● Collect all bestFriendIds and query the database at once ● With the help of dataloader
  • 60. Dataloader - Batching & caching utility Define a batch function Call load() whenever you want to Get data you need import DataLoader from 'dataloader'; const userLoader = new DataLoader( ids => { console.log('Invoked with', ids); return User.getAllByIds(ids); } ); userLoader.load(1).then(console.log) userLoader.load(2).then(console.log) userLoader.load(3).then(console.log) userLoader.load(1).then(console.log) // Outputs: // Invoked with [1,2,3] // {id: 1, ...} // {id: 2, ...} // {id: 3, ...} // {id: 1, ...} from aiodataloader import DataLoader class UserLoader(DataLoader): async def batch_load_fn(self, ids): print('Invoked with %s' % ids) return User.get_all_by_ids(ids) user_loader = UserLoader() future1 = user_loader.load(1) future2 = user_loader.load(2) future3 = user_loader.load(3) future4 = user_loader.load(1) # == future1 # prints: # Invoked with [1, 2, 3] print(await future1) # {id: 1, ...} print(await future2) # {id: 2, ...} print(await future3) # {id: 3, ...} print(await future4) # {id: 1, ...}
  • 61. Batch function class UserLoader(DataLoader): async def batch_load_fn(self, ids): """Fake data loading""" return [{'id': id} for id in ids] ● Maps N IDs to N documents ● Input: IDs ○ Output are cached by ID ● Output: Promise<documents> ● Length of output must match input ○ If not found, return null ○ i-th ID in input should match i-th document in output const userLoader = new DataLoader(ids => { // Fake data loading return Promise.resolve(ids.map(id => ({id}))) })
  • 62. dataloader instance methods ● load(id): ○ input: 1 ID ○ output: a promise that resolves to 1 loaded document ● loadMany(ids): ○ input: N IDs ○ output: a promise that resolves to N loaded documents
  • 63. Multiple dataloader instances ● Prepare a batch function for each data-loading mechanism ● Create a dataloader instance for each batch function ● Batching & caching based on instances user_loader = UserLoader() articles_by_author_id_loader = ArticlesByAuthorIdLoader() # Get user 1's best friends's articles user = await user_loader.load(1) print(await articles_by_author_id_loader.load( user.best_friend_id )) # prints: # [article1, article2, ...] const userLoader = new DataLoader(...) const articlesByAuthorIdLoader = new DataLoader(...) // Get user 1's best friends's articles userLoader.load(1).then( ({bestFriendId}) => articlesByAuthorIdLoader.load(bestFriendId) ).then(console.log) // prints: // [article1, article2, ...]
  • 64. # in root query type user = graphene.Field(User) def resolve_user(obj, args, context): return context['user_loader'].load(args['id']) # in user object type best_friend_articles = graphene.Field(graphene.List(Article)) def resolve_best_friend_articles(obj, args, context): return context['articles_by_author_id_loader'].load( obj['best_friend_id'] ) Combine dataloader with GraphQL schema app.post('/graphql', bodyParser.json(), graphqlExpress(req => ({ schema, context: { userLoader: new DataLoader(...), articleByUserIdLoader: new DataLoader(...) } })) ) // field "user" in root query type { type: User, resolve(obj, {id}, {userLoader}) { return userLoader.load(id); } } // field "bestFriendArticles" in User object type { type: new GraphQLList(Article), resolve({bestFriendId}, args, {articleByUserIdLoader} ) { return articleByUserIdLoader.load(bestFriendId); } } class ViewWithContext(GraphQLView): def get_context(self, request): return { 'user_loader': UserLoader(), 'articles_by_author_id_loader': ArticlesByAuthorIdLoader() } app.add_url_rule( '/graphql', view_func=ViewWithContext.as_view( 'graphql', schema=schema, graphiql=True, executor=AsyncioExecutor) )
  • 65. Summary ● RESTful endpoints --> GraphQL schema fields ● Strongly typed input / output ● Validation & Documentation
  • 66. Other Resources ● "Extended reading" in this slide ● GraphQL official doc ● How to GraphQL online course ● (JS only) generate schema from GraphQL schema language ● Case studies ● FB group - GraphQL Taiwan