SlideShare a Scribd company logo
1 of 66
Download to read 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

More Related Content

What's hot

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
 
Better APIs with GraphQL
Better APIs with GraphQL Better APIs with GraphQL
Better APIs with GraphQL Josh Price
 
Building Modern APIs with GraphQL
Building Modern APIs with GraphQLBuilding Modern APIs with GraphQL
Building Modern APIs with GraphQLAmazon Web Services
 
GraphQL vs REST
GraphQL vs RESTGraphQL vs REST
GraphQL vs RESTGreeceJS
 
REST vs. GraphQL: Critical Look
REST vs. GraphQL: Critical LookREST vs. GraphQL: Critical Look
REST vs. GraphQL: Critical LookNordic APIs
 
Introduction to graphQL
Introduction to graphQLIntroduction to graphQL
Introduction to graphQLMuhilvarnan V
 
Introduction to GraphQL
Introduction to GraphQLIntroduction to GraphQL
Introduction to GraphQLRodrigo Prates
 
Introduction to Graph QL
Introduction to Graph QLIntroduction to Graph QL
Introduction to Graph QLDeepak More
 
Graphql Intro (Tutorial and Example)
Graphql Intro (Tutorial and Example)Graphql Intro (Tutorial and Example)
Graphql Intro (Tutorial and Example)Rafael Wilber Kerr
 
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
 
GraphQL Data Loaders - How to feed your GraphQL API with data the smart way
GraphQL Data Loaders - How to feed your GraphQL API with data the smart wayGraphQL Data Loaders - How to feed your GraphQL API with data the smart way
GraphQL Data Loaders - How to feed your GraphQL API with data the smart wayMirumee Software
 
The Apollo and GraphQL Stack
The Apollo and GraphQL StackThe Apollo and GraphQL Stack
The Apollo and GraphQL StackSashko Stubailo
 

What's hot (20)

GraphQL: Enabling a new generation of API developer tools
GraphQL: Enabling a new generation of API developer toolsGraphQL: Enabling a new generation of API developer tools
GraphQL: Enabling a new generation of API developer tools
 
Introduction to GraphQL
Introduction to GraphQLIntroduction to GraphQL
Introduction to GraphQL
 
React & GraphQL
React & GraphQLReact & GraphQL
React & GraphQL
 
Introduction to GraphQL
Introduction to GraphQLIntroduction to GraphQL
Introduction to GraphQL
 
Better APIs with GraphQL
Better APIs with GraphQL Better APIs with GraphQL
Better APIs with 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 vs REST
GraphQL vs RESTGraphQL vs REST
GraphQL vs REST
 
REST vs. GraphQL: Critical Look
REST vs. GraphQL: Critical LookREST vs. GraphQL: Critical Look
REST vs. GraphQL: Critical Look
 
Introduction to graphQL
Introduction to graphQLIntroduction to graphQL
Introduction to graphQL
 
Graphql
GraphqlGraphql
Graphql
 
Introduction to GraphQL
Introduction to GraphQLIntroduction to GraphQL
Introduction to GraphQL
 
Intro GraphQL
Intro GraphQLIntro GraphQL
Intro GraphQL
 
Introduction to Graph QL
Introduction to Graph QLIntroduction to Graph QL
Introduction to Graph QL
 
Graphql Intro (Tutorial and Example)
Graphql Intro (Tutorial and Example)Graphql Intro (Tutorial and Example)
Graphql Intro (Tutorial and Example)
 
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...
 
GraphQL
GraphQLGraphQL
GraphQL
 
GraphQL Fundamentals
GraphQL FundamentalsGraphQL Fundamentals
GraphQL Fundamentals
 
GraphQL Data Loaders - How to feed your GraphQL API with data the smart way
GraphQL Data Loaders - How to feed your GraphQL API with data the smart wayGraphQL Data Loaders - How to feed your GraphQL API with data the smart way
GraphQL Data Loaders - How to feed your GraphQL API with data the smart way
 
The Apollo and GraphQL Stack
The Apollo and GraphQL StackThe Apollo and GraphQL Stack
The Apollo and GraphQL Stack
 

Similar to 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 to 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
 

Recently uploaded

Salesforce Community Group Quito, Salesforce 101
Salesforce Community Group Quito, Salesforce 101Salesforce Community Group Quito, Salesforce 101
Salesforce Community Group Quito, Salesforce 101Paola De la Torre
 
A Call to Action for Generative AI in 2024
A Call to Action for Generative AI in 2024A Call to Action for Generative AI in 2024
A Call to Action for Generative AI in 2024Results
 
Google AI Hackathon: LLM based Evaluator for RAG
Google AI Hackathon: LLM based Evaluator for RAGGoogle AI Hackathon: LLM based Evaluator for RAG
Google AI Hackathon: LLM based Evaluator for RAGSujit Pal
 
GenCyber Cyber Security Day Presentation
GenCyber Cyber Security Day PresentationGenCyber Cyber Security Day Presentation
GenCyber Cyber Security Day PresentationMichael W. Hawkins
 
Enhancing Worker Digital Experience: A Hands-on Workshop for Partners
Enhancing Worker Digital Experience: A Hands-on Workshop for PartnersEnhancing Worker Digital Experience: A Hands-on Workshop for Partners
Enhancing Worker Digital Experience: A Hands-on Workshop for PartnersThousandEyes
 
Breaking the Kubernetes Kill Chain: Host Path Mount
Breaking the Kubernetes Kill Chain: Host Path MountBreaking the Kubernetes Kill Chain: Host Path Mount
Breaking the Kubernetes Kill Chain: Host Path MountPuma Security, LLC
 
FULL ENJOY 🔝 8264348440 🔝 Call Girls in Diplomatic Enclave | Delhi
FULL ENJOY 🔝 8264348440 🔝 Call Girls in Diplomatic Enclave | DelhiFULL ENJOY 🔝 8264348440 🔝 Call Girls in Diplomatic Enclave | Delhi
FULL ENJOY 🔝 8264348440 🔝 Call Girls in Diplomatic Enclave | Delhisoniya singh
 
IAC 2024 - IA Fast Track to Search Focused AI Solutions
IAC 2024 - IA Fast Track to Search Focused AI SolutionsIAC 2024 - IA Fast Track to Search Focused AI Solutions
IAC 2024 - IA Fast Track to Search Focused AI SolutionsEnterprise Knowledge
 
Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...
Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...
Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...Miguel Araújo
 
My Hashitalk Indonesia April 2024 Presentation
My Hashitalk Indonesia April 2024 PresentationMy Hashitalk Indonesia April 2024 Presentation
My Hashitalk Indonesia April 2024 PresentationRidwan Fadjar
 
Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...
Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...
Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...Igalia
 
Data Cloud, More than a CDP by Matt Robison
Data Cloud, More than a CDP by Matt RobisonData Cloud, More than a CDP by Matt Robison
Data Cloud, More than a CDP by Matt RobisonAnna Loughnan Colquhoun
 
Tech-Forward - Achieving Business Readiness For Copilot in Microsoft 365
Tech-Forward - Achieving Business Readiness For Copilot in Microsoft 365Tech-Forward - Achieving Business Readiness For Copilot in Microsoft 365
Tech-Forward - Achieving Business Readiness For Copilot in Microsoft 3652toLead Limited
 
The Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdf
The Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdfThe Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdf
The Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdfEnterprise Knowledge
 
08448380779 Call Girls In Diplomatic Enclave Women Seeking Men
08448380779 Call Girls In Diplomatic Enclave Women Seeking Men08448380779 Call Girls In Diplomatic Enclave Women Seeking Men
08448380779 Call Girls In Diplomatic Enclave Women Seeking MenDelhi Call girls
 
A Domino Admins Adventures (Engage 2024)
A Domino Admins Adventures (Engage 2024)A Domino Admins Adventures (Engage 2024)
A Domino Admins Adventures (Engage 2024)Gabriella Davis
 
Histor y of HAM Radio presentation slide
Histor y of HAM Radio presentation slideHistor y of HAM Radio presentation slide
Histor y of HAM Radio presentation slidevu2urc
 
The Codex of Business Writing Software for Real-World Solutions 2.pptx
The Codex of Business Writing Software for Real-World Solutions 2.pptxThe Codex of Business Writing Software for Real-World Solutions 2.pptx
The Codex of Business Writing Software for Real-World Solutions 2.pptxMalak Abu Hammad
 
Finology Group – Insurtech Innovation Award 2024
Finology Group – Insurtech Innovation Award 2024Finology Group – Insurtech Innovation Award 2024
Finology Group – Insurtech Innovation Award 2024The Digital Insurer
 
Swan(sea) Song – personal research during my six years at Swansea ... and bey...
Swan(sea) Song – personal research during my six years at Swansea ... and bey...Swan(sea) Song – personal research during my six years at Swansea ... and bey...
Swan(sea) Song – personal research during my six years at Swansea ... and bey...Alan Dix
 

Recently uploaded (20)

Salesforce Community Group Quito, Salesforce 101
Salesforce Community Group Quito, Salesforce 101Salesforce Community Group Quito, Salesforce 101
Salesforce Community Group Quito, Salesforce 101
 
A Call to Action for Generative AI in 2024
A Call to Action for Generative AI in 2024A Call to Action for Generative AI in 2024
A Call to Action for Generative AI in 2024
 
Google AI Hackathon: LLM based Evaluator for RAG
Google AI Hackathon: LLM based Evaluator for RAGGoogle AI Hackathon: LLM based Evaluator for RAG
Google AI Hackathon: LLM based Evaluator for RAG
 
GenCyber Cyber Security Day Presentation
GenCyber Cyber Security Day PresentationGenCyber Cyber Security Day Presentation
GenCyber Cyber Security Day Presentation
 
Enhancing Worker Digital Experience: A Hands-on Workshop for Partners
Enhancing Worker Digital Experience: A Hands-on Workshop for PartnersEnhancing Worker Digital Experience: A Hands-on Workshop for Partners
Enhancing Worker Digital Experience: A Hands-on Workshop for Partners
 
Breaking the Kubernetes Kill Chain: Host Path Mount
Breaking the Kubernetes Kill Chain: Host Path MountBreaking the Kubernetes Kill Chain: Host Path Mount
Breaking the Kubernetes Kill Chain: Host Path Mount
 
FULL ENJOY 🔝 8264348440 🔝 Call Girls in Diplomatic Enclave | Delhi
FULL ENJOY 🔝 8264348440 🔝 Call Girls in Diplomatic Enclave | DelhiFULL ENJOY 🔝 8264348440 🔝 Call Girls in Diplomatic Enclave | Delhi
FULL ENJOY 🔝 8264348440 🔝 Call Girls in Diplomatic Enclave | Delhi
 
IAC 2024 - IA Fast Track to Search Focused AI Solutions
IAC 2024 - IA Fast Track to Search Focused AI SolutionsIAC 2024 - IA Fast Track to Search Focused AI Solutions
IAC 2024 - IA Fast Track to Search Focused AI Solutions
 
Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...
Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...
Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...
 
My Hashitalk Indonesia April 2024 Presentation
My Hashitalk Indonesia April 2024 PresentationMy Hashitalk Indonesia April 2024 Presentation
My Hashitalk Indonesia April 2024 Presentation
 
Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...
Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...
Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...
 
Data Cloud, More than a CDP by Matt Robison
Data Cloud, More than a CDP by Matt RobisonData Cloud, More than a CDP by Matt Robison
Data Cloud, More than a CDP by Matt Robison
 
Tech-Forward - Achieving Business Readiness For Copilot in Microsoft 365
Tech-Forward - Achieving Business Readiness For Copilot in Microsoft 365Tech-Forward - Achieving Business Readiness For Copilot in Microsoft 365
Tech-Forward - Achieving Business Readiness For Copilot in Microsoft 365
 
The Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdf
The Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdfThe Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdf
The Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdf
 
08448380779 Call Girls In Diplomatic Enclave Women Seeking Men
08448380779 Call Girls In Diplomatic Enclave Women Seeking Men08448380779 Call Girls In Diplomatic Enclave Women Seeking Men
08448380779 Call Girls In Diplomatic Enclave Women Seeking Men
 
A Domino Admins Adventures (Engage 2024)
A Domino Admins Adventures (Engage 2024)A Domino Admins Adventures (Engage 2024)
A Domino Admins Adventures (Engage 2024)
 
Histor y of HAM Radio presentation slide
Histor y of HAM Radio presentation slideHistor y of HAM Radio presentation slide
Histor y of HAM Radio presentation slide
 
The Codex of Business Writing Software for Real-World Solutions 2.pptx
The Codex of Business Writing Software for Real-World Solutions 2.pptxThe Codex of Business Writing Software for Real-World Solutions 2.pptx
The Codex of Business Writing Software for Real-World Solutions 2.pptx
 
Finology Group – Insurtech Innovation Award 2024
Finology Group – Insurtech Innovation Award 2024Finology Group – Insurtech Innovation Award 2024
Finology Group – Insurtech Innovation Award 2024
 
Swan(sea) Song – personal research during my six years at Swansea ... and bey...
Swan(sea) Song – personal research during my six years at Swansea ... and bey...Swan(sea) Song – personal research during my six years at Swansea ... and bey...
Swan(sea) Song – personal research during my six years at Swansea ... and bey...
 

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