SlideShare una empresa de Scribd logo
1 de 66
Descargar para leer sin conexión
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

Más contenido relacionado

La actualidad más candente

Graphql presentation
Graphql presentationGraphql presentation
Graphql presentationVibhor Grover
 
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...luisw19
 
Better APIs with GraphQL
Better APIs with GraphQL Better APIs with GraphQL
Better APIs with GraphQL Josh Price
 
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)Hafiz Ismail
 
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 toolsSashko Stubailo
 
How to GraphQL: React Apollo
How to GraphQL: React ApolloHow to GraphQL: React Apollo
How to GraphQL: React ApolloTomasz Bak
 
Graphql Intro (Tutorial and Example)
Graphql Intro (Tutorial and Example)Graphql Intro (Tutorial and Example)
Graphql Intro (Tutorial and Example)Rafael Wilber Kerr
 
Introduction to graphQL
Introduction to graphQLIntroduction to graphQL
Introduction to graphQLMuhilvarnan V
 
Building Modern APIs with GraphQL
Building Modern APIs with GraphQLBuilding Modern APIs with GraphQL
Building Modern APIs with GraphQLAmazon Web Services
 
GraphQL Advanced
GraphQL AdvancedGraphQL Advanced
GraphQL AdvancedLeanIX GmbH
 
Getting started with Apollo Client and GraphQL
Getting started with Apollo Client and GraphQLGetting started with Apollo Client and GraphQL
Getting started with Apollo Client and GraphQLMorgan Dedmon
 
The Apollo and GraphQL Stack
The Apollo and GraphQL StackThe Apollo and GraphQL Stack
The Apollo and GraphQL StackSashko Stubailo
 
Training Series: Build APIs with Neo4j GraphQL Library
Training Series: Build APIs with Neo4j GraphQL LibraryTraining Series: Build APIs with Neo4j GraphQL Library
Training Series: Build APIs with Neo4j GraphQL LibraryNeo4j
 

La actualidad más candente (20)

Graphql presentation
Graphql presentationGraphql presentation
Graphql presentation
 
Introduction to GraphQL
Introduction to GraphQLIntroduction to GraphQL
Introduction to GraphQL
 
GraphQL
GraphQLGraphQL
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...
 
Better APIs with GraphQL
Better APIs with GraphQL Better APIs with GraphQL
Better APIs with 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)
 
Graphql
GraphqlGraphql
Graphql
 
React & GraphQL
React & GraphQLReact & GraphQL
React & 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
 
How to GraphQL: React Apollo
How to GraphQL: React ApolloHow to GraphQL: React Apollo
How to GraphQL: React Apollo
 
Intro GraphQL
Intro GraphQLIntro GraphQL
Intro 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
 
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
 
GraphQL
GraphQLGraphQL
GraphQL
 
GraphQL Advanced
GraphQL AdvancedGraphQL Advanced
GraphQL Advanced
 
Getting started with Apollo Client and GraphQL
Getting started with Apollo Client and GraphQLGetting started with Apollo Client and GraphQL
Getting started with Apollo Client and GraphQL
 
The Apollo and GraphQL Stack
The Apollo and GraphQL StackThe Apollo and GraphQL Stack
The Apollo and GraphQL Stack
 
Training Series: Build APIs with Neo4j GraphQL Library
Training Series: Build APIs with Neo4j GraphQL LibraryTraining Series: Build APIs with Neo4j GraphQL Library
Training Series: Build APIs with Neo4j GraphQL Library
 

Similar a Introduction to GraphQL

[2019-07] GraphQL in depth (serverside)
[2019-07] GraphQL in depth (serverside)[2019-07] GraphQL in depth (serverside)
[2019-07] GraphQL in depth (serverside)croquiscom
 
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, & TypeScriptMongoDB
 
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 GraphQLSoftware Guru
 
GraphQL & Prisma from Scratch
GraphQL & Prisma from ScratchGraphQL & Prisma from Scratch
GraphQL & Prisma from ScratchNikolas Burk
 
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 CloudNikolas Burk
 
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 2017Mike Nakhimovich
 
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 PrismaNikolas Burk
 
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 2017Codemotion
 
GraphQL, Redux, and React
GraphQL, Redux, and ReactGraphQL, Redux, and React
GraphQL, Redux, and ReactKeon Kim
 
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"Fwdays
 
GPerf Using Jesque
GPerf Using JesqueGPerf Using Jesque
GPerf Using Jesquectoestreich
 
Marble Testing RxJS streams
Marble Testing RxJS streamsMarble Testing RxJS streams
Marble Testing RxJS streamsIlia Idakiev
 
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 2013Laurent_VB
 
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 TypescriptMongoDB
 
GraphQL Bangkok Meetup 2.0
GraphQL Bangkok Meetup 2.0GraphQL Bangkok Meetup 2.0
GraphQL Bangkok Meetup 2.0Tobias Meixner
 
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...apidays
 
Testowanie JavaScript
Testowanie JavaScriptTestowanie JavaScript
Testowanie JavaScriptTomasz Bak
 
Extend GraphQL with directives
Extend GraphQL with directivesExtend GraphQL with directives
Extend GraphQL with directivesGreg Bergé
 

Similar 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

"Subclassing and Composition – A Pythonic Tour of Trade-Offs", Hynek Schlawack
"Subclassing and Composition – A Pythonic Tour of Trade-Offs", Hynek Schlawack"Subclassing and Composition – A Pythonic Tour of Trade-Offs", Hynek Schlawack
"Subclassing and Composition – A Pythonic Tour of Trade-Offs", Hynek SchlawackFwdays
 
WordPress Websites for Engineers: Elevate Your Brand
WordPress Websites for Engineers: Elevate Your BrandWordPress Websites for Engineers: Elevate Your Brand
WordPress Websites for Engineers: Elevate Your Brandgvaughan
 
Vector Databases 101 - An introduction to the world of Vector Databases
Vector Databases 101 - An introduction to the world of Vector DatabasesVector Databases 101 - An introduction to the world of Vector Databases
Vector Databases 101 - An introduction to the world of Vector DatabasesZilliz
 
Designing IA for AI - Information Architecture Conference 2024
Designing IA for AI - Information Architecture Conference 2024Designing IA for AI - Information Architecture Conference 2024
Designing IA for AI - Information Architecture Conference 2024Enterprise Knowledge
 
DevoxxFR 2024 Reproducible Builds with Apache Maven
DevoxxFR 2024 Reproducible Builds with Apache MavenDevoxxFR 2024 Reproducible Builds with Apache Maven
DevoxxFR 2024 Reproducible Builds with Apache MavenHervé Boutemy
 
SAP Build Work Zone - Overview L2-L3.pptx
SAP Build Work Zone - Overview L2-L3.pptxSAP Build Work Zone - Overview L2-L3.pptx
SAP Build Work Zone - Overview L2-L3.pptxNavinnSomaal
 
Streamlining Python Development: A Guide to a Modern Project Setup
Streamlining Python Development: A Guide to a Modern Project SetupStreamlining Python Development: A Guide to a Modern Project Setup
Streamlining Python Development: A Guide to a Modern Project SetupFlorian Wilhelm
 
Training state-of-the-art general text embedding
Training state-of-the-art general text embeddingTraining state-of-the-art general text embedding
Training state-of-the-art general text embeddingZilliz
 
Unleash Your Potential - Namagunga Girls Coding Club
Unleash Your Potential - Namagunga Girls Coding ClubUnleash Your Potential - Namagunga Girls Coding Club
Unleash Your Potential - Namagunga Girls Coding ClubKalema Edgar
 
Commit 2024 - Secret Management made easy
Commit 2024 - Secret Management made easyCommit 2024 - Secret Management made easy
Commit 2024 - Secret Management made easyAlfredo García Lavilla
 
Kotlin Multiplatform & Compose Multiplatform - Starter kit for pragmatics
Kotlin Multiplatform & Compose Multiplatform - Starter kit for pragmaticsKotlin Multiplatform & Compose Multiplatform - Starter kit for pragmatics
Kotlin Multiplatform & Compose Multiplatform - Starter kit for pragmaticscarlostorres15106
 
Ensuring Technical Readiness For Copilot in Microsoft 365
Ensuring Technical Readiness For Copilot in Microsoft 365Ensuring Technical Readiness For Copilot in Microsoft 365
Ensuring Technical Readiness For Copilot in Microsoft 3652toLead Limited
 
Human Factors of XR: Using Human Factors to Design XR Systems
Human Factors of XR: Using Human Factors to Design XR SystemsHuman Factors of XR: Using Human Factors to Design XR Systems
Human Factors of XR: Using Human Factors to Design XR SystemsMark Billinghurst
 
What's New in Teams Calling, Meetings and Devices March 2024
What's New in Teams Calling, Meetings and Devices March 2024What's New in Teams Calling, Meetings and Devices March 2024
What's New in Teams Calling, Meetings and Devices March 2024Stephanie Beckett
 
Are Multi-Cloud and Serverless Good or Bad?
Are Multi-Cloud and Serverless Good or Bad?Are Multi-Cloud and Serverless Good or Bad?
Are Multi-Cloud and Serverless Good or Bad?Mattias Andersson
 
Beyond Boundaries: Leveraging No-Code Solutions for Industry Innovation
Beyond Boundaries: Leveraging No-Code Solutions for Industry InnovationBeyond Boundaries: Leveraging No-Code Solutions for Industry Innovation
Beyond Boundaries: Leveraging No-Code Solutions for Industry InnovationSafe Software
 
Artificial intelligence in cctv survelliance.pptx
Artificial intelligence in cctv survelliance.pptxArtificial intelligence in cctv survelliance.pptx
Artificial intelligence in cctv survelliance.pptxhariprasad279825
 
Dev Dives: Streamline document processing with UiPath Studio Web
Dev Dives: Streamline document processing with UiPath Studio WebDev Dives: Streamline document processing with UiPath Studio Web
Dev Dives: Streamline document processing with UiPath Studio WebUiPathCommunity
 
Install Stable Diffusion in windows machine
Install Stable Diffusion in windows machineInstall Stable Diffusion in windows machine
Install Stable Diffusion in windows machinePadma Pradeep
 

Último (20)

"Subclassing and Composition – A Pythonic Tour of Trade-Offs", Hynek Schlawack
"Subclassing and Composition – A Pythonic Tour of Trade-Offs", Hynek Schlawack"Subclassing and Composition – A Pythonic Tour of Trade-Offs", Hynek Schlawack
"Subclassing and Composition – A Pythonic Tour of Trade-Offs", Hynek Schlawack
 
WordPress Websites for Engineers: Elevate Your Brand
WordPress Websites for Engineers: Elevate Your BrandWordPress Websites for Engineers: Elevate Your Brand
WordPress Websites for Engineers: Elevate Your Brand
 
Vector Databases 101 - An introduction to the world of Vector Databases
Vector Databases 101 - An introduction to the world of Vector DatabasesVector Databases 101 - An introduction to the world of Vector Databases
Vector Databases 101 - An introduction to the world of Vector Databases
 
Designing IA for AI - Information Architecture Conference 2024
Designing IA for AI - Information Architecture Conference 2024Designing IA for AI - Information Architecture Conference 2024
Designing IA for AI - Information Architecture Conference 2024
 
DevoxxFR 2024 Reproducible Builds with Apache Maven
DevoxxFR 2024 Reproducible Builds with Apache MavenDevoxxFR 2024 Reproducible Builds with Apache Maven
DevoxxFR 2024 Reproducible Builds with Apache Maven
 
SAP Build Work Zone - Overview L2-L3.pptx
SAP Build Work Zone - Overview L2-L3.pptxSAP Build Work Zone - Overview L2-L3.pptx
SAP Build Work Zone - Overview L2-L3.pptx
 
Streamlining Python Development: A Guide to a Modern Project Setup
Streamlining Python Development: A Guide to a Modern Project SetupStreamlining Python Development: A Guide to a Modern Project Setup
Streamlining Python Development: A Guide to a Modern Project Setup
 
Training state-of-the-art general text embedding
Training state-of-the-art general text embeddingTraining state-of-the-art general text embedding
Training state-of-the-art general text embedding
 
Unleash Your Potential - Namagunga Girls Coding Club
Unleash Your Potential - Namagunga Girls Coding ClubUnleash Your Potential - Namagunga Girls Coding Club
Unleash Your Potential - Namagunga Girls Coding Club
 
Commit 2024 - Secret Management made easy
Commit 2024 - Secret Management made easyCommit 2024 - Secret Management made easy
Commit 2024 - Secret Management made easy
 
Kotlin Multiplatform & Compose Multiplatform - Starter kit for pragmatics
Kotlin Multiplatform & Compose Multiplatform - Starter kit for pragmaticsKotlin Multiplatform & Compose Multiplatform - Starter kit for pragmatics
Kotlin Multiplatform & Compose Multiplatform - Starter kit for pragmatics
 
Ensuring Technical Readiness For Copilot in Microsoft 365
Ensuring Technical Readiness For Copilot in Microsoft 365Ensuring Technical Readiness For Copilot in Microsoft 365
Ensuring Technical Readiness For Copilot in Microsoft 365
 
Human Factors of XR: Using Human Factors to Design XR Systems
Human Factors of XR: Using Human Factors to Design XR SystemsHuman Factors of XR: Using Human Factors to Design XR Systems
Human Factors of XR: Using Human Factors to Design XR Systems
 
What's New in Teams Calling, Meetings and Devices March 2024
What's New in Teams Calling, Meetings and Devices March 2024What's New in Teams Calling, Meetings and Devices March 2024
What's New in Teams Calling, Meetings and Devices March 2024
 
Are Multi-Cloud and Serverless Good or Bad?
Are Multi-Cloud and Serverless Good or Bad?Are Multi-Cloud and Serverless Good or Bad?
Are Multi-Cloud and Serverless Good or Bad?
 
Beyond Boundaries: Leveraging No-Code Solutions for Industry Innovation
Beyond Boundaries: Leveraging No-Code Solutions for Industry InnovationBeyond Boundaries: Leveraging No-Code Solutions for Industry Innovation
Beyond Boundaries: Leveraging No-Code Solutions for Industry Innovation
 
DMCC Future of Trade Web3 - Special Edition
DMCC Future of Trade Web3 - Special EditionDMCC Future of Trade Web3 - Special Edition
DMCC Future of Trade Web3 - Special Edition
 
Artificial intelligence in cctv survelliance.pptx
Artificial intelligence in cctv survelliance.pptxArtificial intelligence in cctv survelliance.pptx
Artificial intelligence in cctv survelliance.pptx
 
Dev Dives: Streamline document processing with UiPath Studio Web
Dev Dives: Streamline document processing with UiPath Studio WebDev Dives: Streamline document processing with UiPath Studio Web
Dev Dives: Streamline document processing with UiPath Studio Web
 
Install Stable Diffusion in windows machine
Install Stable Diffusion in windows machineInstall Stable Diffusion in windows machine
Install Stable Diffusion in windows machine
 

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