1 of 66

Introduction to GraphQL

Johnson Liang

Frontend Engineer, Appier

2017.08.09

This work by Appier Inc is licensed under a Creative Commons Attribution 4.0 International License.

2 of 66

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

3 of 66

graphql.org

4 of 66

[Server]

API shape;

GraphQL Schema

[Client]

Query;

GraphQL

[Client]

Result

5 of 66

Fundamental parts

of a GraphQL server

6 of 66

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 graphenefrom 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 of 66

Runnable GraphQL server code / 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 of 66

Runnable GraphQL server code / 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 of 66

Runnable GraphQL server code / 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 of 66

Runnable GraphQL server code / 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')])

11 of 66

Parts in GraphQL server

GraphQL Server

Entry point

JS: graphql(schema, query)

python: schema.execute(query)

query { serverTime }

Query

{ data: {

serverTime: "..."

} }

Result

Schema

12 of 66

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();� },� },� },� })�});��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)

13 of 66

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 of 66

Ready-made GraphQL Server library

NodeJS

Python

  • Flask-GraphQL

15 of 66

Running Example

16 of 66

GraphiQL

17 of 66

GraphQL Results & error handling

{

serverTime

}

{� "data": {� "serverTime":� "9/5/2016, 6:28:46 PM"� }�}

{� "data": {� "serverTime": null,� },� "errors": {� "message": "...",� "locations": [...]� }�}

{� "errors": [� {� "message": "...",� "locations": [...]� }� ]�}

Query

Result

Runtime Error

Parse Error

18 of 66

Defining API shape -

GraphQL schema

19 of 66

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 of 66

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 of 66

Object Type / Fields

class Query(graphene.ObjectType):� server_time = graphene.Field(...)�� def resolve_server_time(obj, args, context, info):� # ...��schema = graphene.Schema(query=Query)

const schema = new GraphQLSchema({� query: new GraphQLObjectType({� name: 'Query',� fields: {� serverTime: {� type: GraphQLString,� resolve() { /* ...*/ },� },� },� })�});

query {

serverTime

}

22 of 66

Object Type / Fields / 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 of 66

Object Type / Fields / 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 of 66

Object Type / Fields / 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 of 66

Field with a Object Type

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)

query {

serverTime { hour, minute, second }

}

26 of 66

Field with a Object Type

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},� }�});

query {

serverTime { hour, minute, second }

}

27 of 66

Schema, query and output

28 of 66

Real-world example (simplified from Cofacts API server)

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() {...}�

{� data: {� article: {...},� reply: {� author: {...}� }� }�}

schema

query input

output

query��� article��������� reply���� author

29 of 66

Resolving object fields

30 of 66

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)

31 of 66

Resolver function signature

32 of 66

Resolver function signature / obj

  • obj: The previous object, which for a field on the root Query type is often not used.

(Text from official documentation)

33 of 66

Resolver function signature / 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 of 66

How resolver functions are invoked

35 of 66

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 of 66

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 of 66

Resolver function signature / 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)

38 of 66

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 of 66

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 of 66

Resolver function signature / 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)

41 of 66

Context

#: Time object's hour resolverdef resolve_hour(obj, args, context, info):� return ...�

#: Root Qyery type's serverTime resolverdef 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�)

42 of 66

Mutative APIs

43 of 66

query {

createUser(name:"John Doe"){

id

}

}

44 of 66

query {

user(name:"John Doe"){ id }

article(id:123) { text }

}

mutation {

createUser(...) {...}

createArticle(...) {...}

}

Run in parallel

Run in series

45 of 66

Mutations

class CreatePerson(graphene.Mutation):� class Input:� name = graphene.Argument(graphene.String)�� ok = graphene.Field(graphene.Boolean)� person = graphene.Field(lambda: Person)�� @staticmethoddef mutate(root, args, context, info):� #: Should do something that persists#: the new Person here� person = Person(name=args.get('name'))� ok = Truereturn 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 personreturn { ok: true, person };� }� }� }� })�})

46 of 66

Mutations

class CreatePerson(graphene.Mutation):� class Input:� name = graphene.Argument(graphene.String)�� ok = graphene.Field(graphene.Boolean)� person = graphene.Field(lambda: Person)�� @staticmethoddef mutate(root, args, context, info):� #: Should do something that persists#: the new Person here� person = Person(name=args.get('name'))� ok = Truereturn 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 personreturn { ok: true, person };� }� }� }� })�})

47 of 66

Mutations

class CreatePerson(graphene.Mutation):� class Input:� name = graphene.Argument(graphene.String)�� ok = graphene.Field(graphene.Boolean)� person = graphene.Field(lambda: Person)�� @staticmethoddef mutate(root, args, context, info):� #: Should do something that persists#: the new Person here� person = Person(name=args.get('name'))� ok = Truereturn 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 personreturn { ok: true, person };� }� }� }� })�})

48 of 66

Extended reading -- Designing GraphQL Mutations

49 of 66

Making requests to GraphQL Servers

50 of 66

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 of 66

Working with GraphQL Variables (Text from official documentation)

  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

1

2

3

52 of 66

Working with GraphQL Variables (Text from official documentation)

  • Replace the static value in the query with $variableName
  • Declare $variableName as one of the variables accepted by the query
  • Pass variableName: value in the separate, transport-specific (usually JSON) variables dictionary

POST /graphql HTTP/1.1

Content-Type: application/json

{� "query":� "query($offset:TimeInput){serverTimeWithInput(offset:$offset)}",� "variables": {� "offset": { "hour": 3 }� }�}

Some old GraphQL servers only accept "variables" as JSON strings

1

2

3

53 of 66

Clients

  • graphiql (for development)
  • 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 of 66

Advanced query techniques

Extended reading: The Anatomy of GraphQL queries

55 of 66

Solving N+1 Problem:

Dataloader

56 of 66

How resolver functions are invoked

57 of 66

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 of 66

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 of 66

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 of 66

Dataloader - Batching & caching utility

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, ...}

Call load() whenever you want to

Get data you need

61 of 66

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 loadingreturn Promise.resolve(ids.map(id => ({id})))�})

62 of 66

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 of 66

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 of 66

Combine dataloader with GraphQL schema

# 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']� )

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 of 66

Summary

This work by Appier Inc is licensed under a Creative Commons Attribution 4.0 International License.

66 of 66

Other Resources

This work by Appier Inc is licensed under a Creative Commons Attribution 4.0 International License.