SlideShare uma empresa Scribd logo
1 de 66
Baixar para ler offline
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

Mais conteúdo relacionado

Mais procurados

The Apollo and GraphQL Stack
The Apollo and GraphQL StackThe Apollo and GraphQL Stack
The Apollo and GraphQL Stack
Sashko Stubailo
 

Mais procurados (20)

Building Modern APIs with GraphQL
Building Modern APIs with GraphQLBuilding Modern APIs with GraphQL
Building Modern APIs with GraphQL
 
Introduction to GraphQL
Introduction to GraphQLIntroduction to GraphQL
Introduction to GraphQL
 
Intro to GraphQL
 Intro to GraphQL Intro to GraphQL
Intro to GraphQL
 
Graphql presentation
Graphql presentationGraphql presentation
Graphql presentation
 
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
 
Wroclaw GraphQL - GraphQL in Java
Wroclaw GraphQL - GraphQL in JavaWroclaw GraphQL - GraphQL in Java
Wroclaw GraphQL - GraphQL in Java
 
An intro to GraphQL
An intro to GraphQLAn intro to GraphQL
An intro to GraphQL
 
Introduction to GraphQL: Mobile Week SF
Introduction to GraphQL: Mobile Week SFIntroduction to GraphQL: Mobile Week SF
Introduction to GraphQL: Mobile Week SF
 
GraphQL
GraphQLGraphQL
GraphQL
 
Graphql
GraphqlGraphql
Graphql
 
Introduction to GraphQL
Introduction to GraphQLIntroduction to GraphQL
Introduction to GraphQL
 
Introduction to graphQL
Introduction to graphQLIntroduction to graphQL
Introduction to graphQL
 
Intro GraphQL
Intro GraphQLIntro GraphQL
Intro GraphQL
 
GraphQL as an alternative approach to REST (as presented at Java2Days/CodeMon...
GraphQL as an alternative approach to REST (as presented at Java2Days/CodeMon...GraphQL as an alternative approach to REST (as presented at Java2Days/CodeMon...
GraphQL as an alternative approach to REST (as presented at Java2Days/CodeMon...
 
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)
 
GraphQL Fundamentals
GraphQL FundamentalsGraphQL Fundamentals
GraphQL Fundamentals
 
React & GraphQL
React & GraphQLReact & GraphQL
React & GraphQL
 
The Apollo and GraphQL Stack
The Apollo and GraphQL StackThe Apollo and GraphQL Stack
The Apollo and GraphQL Stack
 
GraphQL vs REST
GraphQL vs RESTGraphQL vs REST
GraphQL vs REST
 
Introduction to GraphQL
Introduction to GraphQLIntroduction to GraphQL
Introduction to GraphQL
 

Semelhante a Introduction to GraphQL

Semelhante a 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
 

Último

Architecting Cloud Native Applications
Architecting Cloud Native ApplicationsArchitecting Cloud Native Applications
Architecting Cloud Native Applications
WSO2
 
+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...
+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...
+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...
?#DUbAI#??##{{(☎️+971_581248768%)**%*]'#abortion pills for sale in dubai@
 
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
 

Último (20)

Mcleodganj Call Girls 🥰 8617370543 Service Offer VIP Hot Model
Mcleodganj Call Girls 🥰 8617370543 Service Offer VIP Hot ModelMcleodganj Call Girls 🥰 8617370543 Service Offer VIP Hot Model
Mcleodganj Call Girls 🥰 8617370543 Service Offer VIP Hot Model
 
AWS Community Day CPH - Three problems of Terraform
AWS Community Day CPH - Three problems of TerraformAWS Community Day CPH - Three problems of Terraform
AWS Community Day CPH - Three problems of Terraform
 
Apidays New York 2024 - The value of a flexible API Management solution for O...
Apidays New York 2024 - The value of a flexible API Management solution for O...Apidays New York 2024 - The value of a flexible API Management solution for O...
Apidays New York 2024 - The value of a flexible API Management solution for O...
 
Platformless Horizons for Digital Adaptability
Platformless Horizons for Digital AdaptabilityPlatformless Horizons for Digital Adaptability
Platformless Horizons for Digital Adaptability
 
Architecting Cloud Native Applications
Architecting Cloud Native ApplicationsArchitecting Cloud Native Applications
Architecting Cloud Native Applications
 
ProductAnonymous-April2024-WinProductDiscovery-MelissaKlemke
ProductAnonymous-April2024-WinProductDiscovery-MelissaKlemkeProductAnonymous-April2024-WinProductDiscovery-MelissaKlemke
ProductAnonymous-April2024-WinProductDiscovery-MelissaKlemke
 
Exploring Multimodal Embeddings with Milvus
Exploring Multimodal Embeddings with MilvusExploring Multimodal Embeddings with Milvus
Exploring Multimodal Embeddings with Milvus
 
Strategize a Smooth Tenant-to-tenant Migration and Copilot Takeoff
Strategize a Smooth Tenant-to-tenant Migration and Copilot TakeoffStrategize a Smooth Tenant-to-tenant Migration and Copilot Takeoff
Strategize a Smooth Tenant-to-tenant Migration and Copilot Takeoff
 
+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...
+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...
+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...
 
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 New York 2024 - Scaling API-first by Ian Reasor and Radu Cotescu, Adobe
Apidays New York 2024 - Scaling API-first by Ian Reasor and Radu Cotescu, AdobeApidays New York 2024 - Scaling API-first by Ian Reasor and Radu Cotescu, Adobe
Apidays New York 2024 - Scaling API-first by Ian Reasor and Radu Cotescu, Adobe
 
Artificial Intelligence Chap.5 : Uncertainty
Artificial Intelligence Chap.5 : UncertaintyArtificial Intelligence Chap.5 : Uncertainty
Artificial Intelligence Chap.5 : Uncertainty
 
Elevate Developer Efficiency & build GenAI Application with Amazon Q​
Elevate Developer Efficiency & build GenAI Application with Amazon Q​Elevate Developer Efficiency & build GenAI Application with Amazon Q​
Elevate Developer Efficiency & build GenAI Application with Amazon Q​
 
Understanding the FAA Part 107 License ..
Understanding the FAA Part 107 License ..Understanding the FAA Part 107 License ..
Understanding the FAA Part 107 License ..
 
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
 
[BuildWithAI] Introduction to Gemini.pdf
[BuildWithAI] Introduction to Gemini.pdf[BuildWithAI] Introduction to Gemini.pdf
[BuildWithAI] Introduction to Gemini.pdf
 
Boost Fertility New Invention Ups Success Rates.pdf
Boost Fertility New Invention Ups Success Rates.pdfBoost Fertility New Invention Ups Success Rates.pdf
Boost Fertility New Invention Ups Success Rates.pdf
 
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
 
Six Myths about Ontologies: The Basics of Formal Ontology
Six Myths about Ontologies: The Basics of Formal OntologySix Myths about Ontologies: The Basics of Formal Ontology
Six Myths about Ontologies: The Basics of Formal Ontology
 
CNIC Information System with Pakdata Cf In Pakistan
CNIC Information System with Pakdata Cf In PakistanCNIC Information System with Pakdata Cf In Pakistan
CNIC Information System with Pakdata Cf In Pakistan
 

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