SlideShare una empresa de Scribd logo
1 de 40
Descargar para leer sin conexión
-
GraphQL

in Depth

(serverside)
크로키닷컴㈜ 윤상민
2019.7.4
➢ Just a query language
GraphQL is …
type User {
id: ID!
name: String!
}
type Query {
user(id: ID): User
}
query {
user(id: "10") {
id
name
}
}
{
"data": {
"user": {
"id": "10",
"name": "Croquis"
}
}
}
+
➢ So, following is only for GraphQL.js.
GraphQL is …
import { GraphQLObjectType, GraphQLSchema,
GraphQLString, printSchema } from 'graphql';
const schema = new GraphQLSchema({
query: new GraphQLObjectType({
name: 'RootQueryType',
fields: {
hello: {
type: GraphQLString,
},
},
}),
});
console.log(printSchema(schema));
Define Schema: raw object
schema {
query: RootQueryType
}
type RootQueryType {
hello: String
}
import { GraphQLList, GraphQLNonNull, GraphQLObjectType,
GraphQLSchema, GraphQLString } from 'graphql';
const user = new GraphQLObjectType({
name: 'User', fields: {
name: {
type: GraphQLNonNull(GraphQLString),
} } });
const schema = new GraphQLSchema({
query: new GraphQLObjectType({
name: 'Query',
fields: {
users: {
type: GraphQLNonNull(GraphQLList(GraphQLNonNull(user))),
} } }) });
Define Schema: custom type
type Query {
users: [User!]!
}
type User {
name: String!
}
import { buildSchema } from 'graphql';
const schema = buildSchema(`
type User {
name: String!
}
type Query {
users: [User!]!
}
`);
Define Schema: using GraphQL schema language
import 'reflect-metadata';
import { buildSchemaSync, Field, ObjectType, Query, Resolver } from 'type-graphql';
@ObjectType()
class User {
@Field()
name: string;
}
@Resolver()
class UserResolver {
@Query((returns) => [User])
users() {
return [];
}
}
const schema = buildSchemaSync({
resolvers: [UserResolver],
});
Define Schema: TypeGraphQL
import { graphql, GraphQLObjectType, GraphQLSchema, GraphQLString } from 'graphql';
const schema = new GraphQLSchema({
query: new GraphQLObjectType({
name: 'RootQueryType',
fields: {
hello: {
type: GraphQLString,
resolve: () => 'World',
},
},
}),
});
console.log(await graphql(schema, '{ hello }’));
// { data: { hello: 'World' } }
Execute Query
// from graphql-express
import { Source, parse, execute } from 'graphql';
function graphqlHTTP(options: Options): Middleware {
return function graphqlMiddleware(request: $Request, response: $Response) {
return getGraphQLParams(request)
.then(() => {
const source = new Source(query, 'GraphQL request');
documentAST = parse(source);
return execute({ schema, documentAST, rootValue, context, variables, operationName });
})
.then(result => {
if (response.statusCode === 200 && result && !result.data) {
response.statusCode = 500;
}
const payload = JSON.stringify(result, null, pretty ? 2 : 0);
sendResponse(response, 'application/json', payload);
}); }; }
Transport Layer
➢ All other things are how to resolve values
Resolve
import { graphql, GraphQLObjectType, GraphQLSchema, GraphQLString } from 'graphql';
const schema = new GraphQLSchema({
query: new GraphQLObjectType({
name: 'RootQueryType',
fields: {
hello: {
type: GraphQLString,
resolve: () => 'World',
},
},
}),
});
console.log(await graphql(schema, '{ hello }'));
Basic Resolver
import { buildSchema, graphql } from 'graphql';
const schema = buildSchema(`
type Query {
hello: String
}
`);
console.log(await graphql(schema, '{ hello }', {
hello: () => 'World',
}));
Root Value
var defaultFieldResolver = function defaultFieldResolver(source, args, contextValue,
info) {
// ensure source is a value for which property access is acceptable.
if (_typeof(source) === 'object' || typeof source === 'function') {
var property = source[info.fieldName];
if (typeof property === 'function') {
return source[info.fieldName](args, contextValue, info);
}
return property;
}
};
Root Value: internal
import { buildSchema, graphql } from 'graphql';
const schema = buildSchema(`
type Query {
hello: String
}
`);
schema.getQueryType()!.getFields().hello.resolve = () => {
return 'World';
};
console.log(await graphql(schema, '{ hello }'));
Attach Resolver After Creation
import { buildSchema, graphql } from 'graphql';
import { addResolveFunctionsToSchema } from 'graphql-tools';
const schema = buildSchema(`
type Query {
hello: String
}
`);
const resolvers = {
Query: {
hello: () => {
return 'World';
} } };
addResolveFunctionsToSchema({ schema, resolvers });
console.log(await graphql(schema, '{ hello }'));
Attach Resolver After Creation: graphql-tools
// OR
const resolvers = {
Query: {
hello: {
type: GraphQLNonNull(GraphQLString),
resolve() {
return 'World';
},
} } };
➢ Function signature: resolver(obj, args, context, info)
✓ For root value: (args, context, info)
➢ Call defaultFieldResolver if not defined
➢ Always called even if object has a field value
Resolver
const schema = buildSchema(`
type User { name: String! }
type Query { users: [User!]! }`);
const resolvers = {
Query: {
users(obj) {
return [{ name: 'Name1' }, { name: 'Name2' }];
} },
User: {
name(obj) {
return obj.name.toLowerCase();
} } };
console.log(await graphql(schema, '{ users { name } }', { foo: 1 }));
// { data: { users: [ { name: 'name1' }, { name: 'name2' } ] } }
Resolver when object has a field value
➢ Contains the result returned from the resolver on the parent field
✓ Root Value for Query field
➢ @Root() in TypeGraphQL
Resolver Arguments: 1) obj (source, parent)
const schema = buildSchema(`
type User { name: String! }
type Query { users: [User!]! }`);
const resolvers = {
Query: {
users(obj) { // obj === {foo: 1}
return [{ id: 1 }, { id: 2 }];
} },
User: {
name(obj) { /// obj === {id: 1} or {id: 2}
return `Name${obj.id}`;
} } };
console.log(await graphql(schema, '{ users { name } }', { foo: 1 }));
// { data: { users: [ { name: 'Name1' }, { name: 'Name2' } ] } }
Resolver Arguments: 1) obj (source, parent)
➢ An object with the arguments passed into the field in the query
➢ @Arg() or @Args() in TypeGraphQL
Resolver Arguments: 2) args
const schema = buildSchema(`
type User { name(length: Int): String! }
type Query { users(query: String, length: Int): [User!]! }`);
const resolvers = {
Query: {
users(obj, args) { // args === { query: 'n', length: 2 }
return [{ name: 'Daniel' }, { name: 'Johnny' }];
} },
User: {
name(obj, args) { // args === { length: 3 }
return obj.name.substr(0, args.length);
} } };
console.log(await graphql(schema, '{ users(query: "N", length: 2) { name(length:
3) } }'));
// { data: { users: [ { name: 'Dan' }, { name: 'Joh' } ] } }
Resolver Arguments: 2) args
➢ An object shared by all resolvers in a particular query
➢ Per-request
➢ For examples:
✓ authentication information
✓ dataloader instances
➢ @Ctx() in TypeGraphQL
Resolver Arguments: 3) context
const schema = buildSchema(`
type User { name: String! }
type Query { users: [User!]! }`);
const resolvers = {
Query: {
users(obj, args, context) { // context === { foo: 1 }
context.bar = 1;
return [{ name: 'Name1' }, { name: 'Name2' }];
} },
User: {
name(obj, args, context) { // context === { foo: 1, bar: 1 } or { foo: 1, bar: 2 }
context.bar++;
return obj.name;
} } };
console.log(await graphql(schema, '{ users { name } }', null, { foo: 1 }));
// { data: { users: [ { name: 'Name1' }, { name: 'Name2' } ] } }
Resolver Arguments: 3) context
➢ Contains information about the execution state of the query
➢ @Info in TypeGraphQL
➢ Some utilities: graphql-list-fields, graphql-fields-list, @croquiscom/crary-graphql
Resolver Arguments: 4) info
export interface GraphQLResolveInfo {
readonly fieldName: string;
readonly fieldNodes: ReadonlyArray<FieldNode>;
readonly returnType: GraphQLOutputType;
readonly parentType: GraphQLObjectType;
readonly path: ResponsePath;
readonly schema: GraphQLSchema;
readonly fragments: { [key: string]: FragmentDefinitionNode };
readonly rootValue: any;
readonly operation: OperationDefinitionNode;
readonly variableValues: { [variableName: string]: any };
}
const resolvers = {
Query: {
users(obj, args, context, info) {
// info.fieldName === 'users'
// print(info.fieldNodes[0]) === 'users { name }'
// info.parentType === Query
// info.returnType === [User!]!
return [{ name: 'Name1' }, { name: 'Name2' }];
} },
User: {
name(obj, args, context, info) {
// info.fieldName === 'name'
// print(info.fieldNodes[0]) === 'name'
// info.parentType === User
// info.returnType === String!
return obj.name;
} } };
Resolver Arguments: 4) info
import { addArgumentToInfo, getFieldList, getFieldList1st, getFieldString } from '@croquiscom/crary-graphql';
const schema = buildSchema(`
type Name { first: String, last: String }
type User { name: Name, age: Int }
type Query { users: [User!]! }`);
const resolvers = {
Query: {
users(obj, args, context, info) {
// getFieldList(info) === [ 'name.first', 'name.last', 'age' ]
// getFieldList1st(info)) === [ 'name', 'age' ]
// getFieldString(info) === 'name { first last } age'
// print(info.fieldNodes[0]) === 'users { name { first last } age }'
// info.variableValues === {}
const newInfo = addArgumentToInfo(info, 'length', 3, GraphQLInt);
// print(newInfo.fieldNodes[0]) === 'users(length: $_c_length) { name { first last } age }'
// newInfo.variableValues === { _c_length: 3 }
return [];
} } };
console.log(await graphql(schema, '{ users { name { first last } age } }'));
Resolver Arguments: 4) info - @croquiscom/crary-graphql
➢ buildSchema + addResolveFunctionsToSchema
Advanced: makeExecutableSchema
const typeDefs = `
type User { name: String! }
type Query { users: [User!]! }`;
const resolvers = {
Query: {
users() {
return [{ name: 'Name1' }, { name: 'Name2' }];
} },
User: {
name(obj) {
return obj.name;
} } };
const schema = makeExecutableSchema({ typeDefs, resolvers });
console.log(await graphql(schema, '{ users { name } }'));
➢ Generate GraphQL schema objects that delegate to a remote server
Advanced: makeRemoteExecutableSchema
import { HttpLink } from 'apollo-link-http';
import { importSchema } from 'graphql-import';
import { makeRemoteExecutableSchema } from 'graphql-tools';
import fetch from 'node-fetch';
const schema = makeRemoteExecutableSchema({
schema: importSchema(`${__dirname}/schema.graphql`),
link: new HttpLink({
uri: 'http://other.service/graphql',
fetch: fetch,
}),
});
export default function linkToFetcher(link: ApolloLink): Fetcher {
return (fetcherOperation: FetcherOperation) => {
return makePromise(execute(link, fetcherOperation as GraphQLRequest));
}; }
export function createResolver(fetcher: Fetcher): GraphQLFieldResolver<any, any> {
return async (root, args, context, info) => {
const fragments = Object.keys(info.fragments).map(
fragment => info.fragments[fragment],
);
const document = {
kind: Kind.DOCUMENT,
definitions: [info.operation, ...fragments],
};
const result = await fetcher({
query: document,
variables: info.variableValues,
context: { graphqlContext: context },
});
return checkResultAndHandleErrors(result, info);
}; }
Advanced: makeRemoteExecutableSchema internal
➢ Combine multiple GraphQL schemas together
Advanced: mergeSchemas
const user_schema = buildSchema(`
type User { id: ID!, name: String! }
type Query { users: [User!]! }`);
const post_schema = buildSchema(`
type Post { contents: String! }
type Query { posts(user_id: ID): [Post!]! }`);
const schema = mergeSchemas({
schemas: [
user_schema,
post_schema,
`extend type User { posts: [Post!]! }`,
],
});
type Post {
contents: String!
}
type Query {
users: [User!]!
posts(user_id: ID): [Post!]!
}
type User {
id: ID!
name: String!
posts: [Post!]!
}
User: {
posts: {
fragment: '... on User { id }',
resolve(obj, args, context, info) {
console.log(print(info.fieldNodes[0]));
return info.mergeInfo.delegateToSchema({
schema: post_schema,
operation: 'query',
fieldName: 'posts',
args: { user_id: obj.id },
context,
info,
});
} } }

// for query '{ users { name posts { contents } } }'
// 1. 'users { name ... on User { id } }' on user
// 2. 'posts { contents }' on merged
// 3. 'posts(user_id: $_v0_user_id) { contents }' on post
Advanced: mergeSchemas + delegateToSchema
➢ Merged schema must have all operations for delegateToSchema
Advanced: manual delegation
resolve(obj, args, context, info) {
info = transformInfoForSchema(info, post_schema, info.mergeInfo.fragments);
const fields = getFieldString(info);
const query = `query($user_id: ID) { posts(user_id: $user_id) { ${fields} } }`;
const variables = { user_id: obj.id };
return fetch('http://post/graphql', {
method: 'POST',
body: JSON.stringify({ query, variables }),
headers: {
'Content-Type': 'application/json',
},
});
},
directive @authorized on FIELD_DEFINITION
type Query {
posts: [Post!]! @authorized
}
import { SchemaDirectiveVisitor } from 'graphql-tools';
class AuthorizedDirective extends SchemaDirectiveVisitor {
visitFieldDefinition(field: GraphQLField<any, any>) {
const { resolve = defaultFieldResolver } = field;
field.resolve = async (source, args, context, info) => {
checkAuthorized(context);
const result = await resolve(source, args, context, info);
return result;
};
}
}
SchemaDirectiveVisitor.visitSchemaDirectives(schema, { authorized: AuthorizedDirective });
Advanced: directive
➢ Use DataLoader
➢ Batch function must return an array of values that have same order to an array of
keys
➢ It is common to create a new DataLoader per request (context will be helpful)
Advanced: N+1 problem
import DataLoader from 'dataloader';
resolve(parent, args, context, info) {
if (!context.loader) {
context.loader = new DataLoader((keys) => {
return ['value for keys[0]', 'value for keys[1]', ...];
});
}
return context.loader.load(key);
},
➢ Subscription is a one-way communication from server to client.
✓ Socket.io is a two-way
➢ You need to implement ‘subscribe’ method that returns AsyncIterator
Advanced: subscription
import { PubSub } from 'graphql-subscriptions';
const pubSub = new PubSub();
async function doTask(task_id: number) {
for (let i = 0; i < 10; i++) {
await Bluebird.delay(1000);
pubSub.publish(`doTask-${task_id}`, i);
}
pubSub.publish(`doTask-${task_id}`, 'done');
}
const schema = new GraphQLSchema({
subscription: new GraphQLObjectType({
name: 'RootSubscriptionType',
fields: {
doTaskProgressed: {
type: GraphQLString,
args: { task_id: { type: new GraphQLNonNull(GraphQLInt) } },
subscribe: (source, args) => {
const task_id = args.task_id;
return pubSub.asyncIterator(`doTask-${task_id}`);
},
} } }) });
Advanced: subscription - run task in separate thread
- Task will run regardless subscription
- You need ‘done’ to notify task ended
const schema = new GraphQLSchema({
subscription: new GraphQLObjectType({
name: 'RootSubscriptionType',
fields: {
doTaskWithProgress: {
type: GraphQLString,
async *subscribe(source, args) {
for (let i = 0; i < 10; i++) {
await Bluebird.delay(1000);
yield i;
}
},
} } }) });
Advanced: subscription - run task in subscribe
- Task will stop when subscription ended
- Subscription will be ended when task ended
➢ graphql-resolvers
✓ combineResolvers, pipeResolvers, allResolvers
✓ resolveDependee, resolveDependees, isDependee
➢ graphql-scalars
✓ DateTime, EmailAddress, NegativeInt, PostalCode, URL, …
➢ Apollo Federation
✓ Replace schema stitching (remote + merge)
Advanced: other packages
➢ GraphQL Resolvers: Best Practices
➢
Further Reading
-
Thank you
for your attention

Más contenido relacionado

La actualidad más candente

C# Starter L02-Classes and Objects
C# Starter L02-Classes and ObjectsC# Starter L02-Classes and Objects
C# Starter L02-Classes and Objects
Mohammad Shaker
 
Declarative Internal DSLs in Lua: A Game Changing Experience
Declarative Internal DSLs in Lua: A Game Changing ExperienceDeclarative Internal DSLs in Lua: A Game Changing Experience
Declarative Internal DSLs in Lua: A Game Changing Experience
Alexander Gladysh
 
Refactoring to Macros with Clojure
Refactoring to Macros with ClojureRefactoring to Macros with Clojure
Refactoring to Macros with Clojure
Dmitry Buzdin
 
Poor Man's Functional Programming
Poor Man's Functional ProgrammingPoor Man's Functional Programming
Poor Man's Functional Programming
Dmitry Buzdin
 
Json and SQL DB serialization Introduction with Play! and Slick
Json and SQL DB serialization Introduction with Play! and SlickJson and SQL DB serialization Introduction with Play! and Slick
Json and SQL DB serialization Introduction with Play! and Slick
Stephen Kemmerling
 
Java весна 2013 лекция 2
Java весна 2013 лекция 2Java весна 2013 лекция 2
Java весна 2013 лекция 2
Technopark
 

La actualidad más candente (20)

3. Объекты, классы и пакеты в Java
3. Объекты, классы и пакеты в Java3. Объекты, классы и пакеты в Java
3. Объекты, классы и пакеты в Java
 
PDBC
PDBCPDBC
PDBC
 
C# Starter L02-Classes and Objects
C# Starter L02-Classes and ObjectsC# Starter L02-Classes and Objects
C# Starter L02-Classes and Objects
 
Network
NetworkNetwork
Network
 
Why Sifu
Why SifuWhy Sifu
Why Sifu
 
Declarative Internal DSLs in Lua: A Game Changing Experience
Declarative Internal DSLs in Lua: A Game Changing ExperienceDeclarative Internal DSLs in Lua: A Game Changing Experience
Declarative Internal DSLs in Lua: A Game Changing Experience
 
Node js mongodriver
Node js mongodriverNode js mongodriver
Node js mongodriver
 
C# 7
C# 7C# 7
C# 7
 
Refactoring to Macros with Clojure
Refactoring to Macros with ClojureRefactoring to Macros with Clojure
Refactoring to Macros with Clojure
 
Category theory, Monads, and Duality in the world of (BIG) Data
Category theory, Monads, and Duality in the world of (BIG) DataCategory theory, Monads, and Duality in the world of (BIG) Data
Category theory, Monads, and Duality in the world of (BIG) Data
 
Bind me if you can
Bind me if you canBind me if you can
Bind me if you can
 
Poor Man's Functional Programming
Poor Man's Functional ProgrammingPoor Man's Functional Programming
Poor Man's Functional Programming
 
Json and SQL DB serialization Introduction with Play! and Slick
Json and SQL DB serialization Introduction with Play! and SlickJson and SQL DB serialization Introduction with Play! and Slick
Json and SQL DB serialization Introduction with Play! and Slick
 
Dart London hackathon
Dart  London hackathonDart  London hackathon
Dart London hackathon
 
JavaScript - i och utanför webbläsaren (2010-03-03)
JavaScript - i och utanför webbläsaren (2010-03-03)JavaScript - i och utanför webbläsaren (2010-03-03)
JavaScript - i och utanför webbläsaren (2010-03-03)
 
JavaScript Arrays
JavaScript Arrays JavaScript Arrays
JavaScript Arrays
 
TypeScript - All you ever wanted to know - Tech Talk by Epic Labs
TypeScript - All you ever wanted to know - Tech Talk by Epic LabsTypeScript - All you ever wanted to know - Tech Talk by Epic Labs
TypeScript - All you ever wanted to know - Tech Talk by Epic Labs
 
C# 7.0 Hacks and Features
C# 7.0 Hacks and FeaturesC# 7.0 Hacks and Features
C# 7.0 Hacks and Features
 
Elm: give it a try
Elm: give it a tryElm: give it a try
Elm: give it a try
 
Java весна 2013 лекция 2
Java весна 2013 лекция 2Java весна 2013 лекция 2
Java весна 2013 лекция 2
 

Similar a [2019-07] GraphQL in depth (serverside)

HelsinkiJS meet-up. Dmitry Soshnikov - ECMAScript 6
HelsinkiJS meet-up. Dmitry Soshnikov - ECMAScript 6HelsinkiJS meet-up. Dmitry Soshnikov - ECMAScript 6
HelsinkiJS meet-up. Dmitry Soshnikov - ECMAScript 6
Dmitry Soshnikov
 
Functional programming using underscorejs
Functional programming using underscorejsFunctional programming using underscorejs
Functional programming using underscorejs
偉格 高
 
Apache Flink Training: DataStream API Part 2 Advanced
Apache Flink Training: DataStream API Part 2 Advanced Apache Flink Training: DataStream API Part 2 Advanced
Apache Flink Training: DataStream API Part 2 Advanced
Flink Forward
 
Better Software: introduction to good code
Better Software: introduction to good codeBetter Software: introduction to good code
Better Software: introduction to good code
Giordano Scalzo
 

Similar a [2019-07] GraphQL in depth (serverside) (20)

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
 
Graphql, REST and Apollo
Graphql, REST and ApolloGraphql, REST and Apollo
Graphql, REST and Apollo
 
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"
 
HelsinkiJS meet-up. Dmitry Soshnikov - ECMAScript 6
HelsinkiJS meet-up. Dmitry Soshnikov - ECMAScript 6HelsinkiJS meet-up. Dmitry Soshnikov - ECMAScript 6
HelsinkiJS meet-up. Dmitry Soshnikov - ECMAScript 6
 
Introduction to GraphQL
Introduction to GraphQLIntroduction to GraphQL
Introduction to GraphQL
 
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
 
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
 
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
 
The Future of JVM Languages
The Future of JVM Languages The Future of JVM Languages
The Future of JVM Languages
 
Developing and maintaining a Java GraphQL back-end: The less obvious - Bojan ...
Developing and maintaining a Java GraphQL back-end: The less obvious - Bojan ...Developing and maintaining a Java GraphQL back-end: The less obvious - Bojan ...
Developing and maintaining a Java GraphQL back-end: The less obvious - Bojan ...
 
Functional programming using underscorejs
Functional programming using underscorejsFunctional programming using underscorejs
Functional programming using underscorejs
 
TypeScript Introduction
TypeScript IntroductionTypeScript Introduction
TypeScript Introduction
 
Best of build 2021 - C# 10 & .NET 6
Best of build 2021 -  C# 10 & .NET 6Best of build 2021 -  C# 10 & .NET 6
Best of build 2021 - C# 10 & .NET 6
 
Ad java prac sol set
Ad java prac sol setAd java prac sol set
Ad java prac sol set
 
Apache Flink Training: DataStream API Part 2 Advanced
Apache Flink Training: DataStream API Part 2 Advanced Apache Flink Training: DataStream API Part 2 Advanced
Apache Flink Training: DataStream API Part 2 Advanced
 
GraphQL & Prisma from Scratch
GraphQL & Prisma from ScratchGraphQL & Prisma from Scratch
GraphQL & Prisma from Scratch
 
Neo4j GraphTour: Utilizing Powerful Extensions for Analytics and Operations
Neo4j GraphTour: Utilizing Powerful Extensions for Analytics and OperationsNeo4j GraphTour: Utilizing Powerful Extensions for Analytics and Operations
Neo4j GraphTour: Utilizing Powerful Extensions for Analytics and Operations
 
GraphQL Bangkok Meetup 2.0
GraphQL Bangkok Meetup 2.0GraphQL Bangkok Meetup 2.0
GraphQL Bangkok Meetup 2.0
 
GraphQL Los Angeles Meetup Slides
GraphQL Los Angeles Meetup SlidesGraphQL Los Angeles Meetup Slides
GraphQL Los Angeles Meetup Slides
 
Better Software: introduction to good code
Better Software: introduction to good codeBetter Software: introduction to good code
Better Software: introduction to good code
 

Último

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
vu2urc
 

Último (20)

The 7 Things I Know About Cyber Security After 25 Years | April 2024
The 7 Things I Know About Cyber Security After 25 Years | April 2024The 7 Things I Know About Cyber Security After 25 Years | April 2024
The 7 Things I Know About Cyber Security After 25 Years | April 2024
 
Handwritten Text Recognition for manuscripts and early printed texts
Handwritten Text Recognition for manuscripts and early printed textsHandwritten Text Recognition for manuscripts and early printed texts
Handwritten Text Recognition for manuscripts and early printed texts
 
Boost PC performance: How more available memory can improve productivity
Boost PC performance: How more available memory can improve productivityBoost PC performance: How more available memory can improve productivity
Boost PC performance: How more available memory can improve productivity
 
Axa Assurance Maroc - Insurer Innovation Award 2024
Axa Assurance Maroc - Insurer Innovation Award 2024Axa Assurance Maroc - Insurer Innovation Award 2024
Axa Assurance Maroc - Insurer Innovation Award 2024
 
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...
 
Connector Corner: Accelerate revenue generation using UiPath API-centric busi...
Connector Corner: Accelerate revenue generation using UiPath API-centric busi...Connector Corner: Accelerate revenue generation using UiPath API-centric busi...
Connector Corner: Accelerate revenue generation using UiPath API-centric busi...
 
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
 
A Domino Admins Adventures (Engage 2024)
A Domino Admins Adventures (Engage 2024)A Domino Admins Adventures (Engage 2024)
A Domino Admins Adventures (Engage 2024)
 
Understanding Discord NSFW Servers A Guide for Responsible Users.pdf
Understanding Discord NSFW Servers A Guide for Responsible Users.pdfUnderstanding Discord NSFW Servers A Guide for Responsible Users.pdf
Understanding Discord NSFW Servers A Guide for Responsible Users.pdf
 
Powerful Google developer tools for immediate impact! (2023-24 C)
Powerful Google developer tools for immediate impact! (2023-24 C)Powerful Google developer tools for immediate impact! (2023-24 C)
Powerful Google developer tools for immediate impact! (2023-24 C)
 
04-2024-HHUG-Sales-and-Marketing-Alignment.pptx
04-2024-HHUG-Sales-and-Marketing-Alignment.pptx04-2024-HHUG-Sales-and-Marketing-Alignment.pptx
04-2024-HHUG-Sales-and-Marketing-Alignment.pptx
 
Exploring the Future Potential of AI-Enabled Smartphone Processors
Exploring the Future Potential of AI-Enabled Smartphone ProcessorsExploring the Future Potential of AI-Enabled Smartphone Processors
Exploring the Future Potential of AI-Enabled Smartphone Processors
 
2024: Domino Containers - The Next Step. News from the Domino Container commu...
2024: Domino Containers - The Next Step. News from the Domino Container commu...2024: Domino Containers - The Next Step. News from the Domino Container commu...
2024: Domino Containers - The Next Step. News from the Domino Container commu...
 
GenAI Risks & Security Meetup 01052024.pdf
GenAI Risks & Security Meetup 01052024.pdfGenAI Risks & Security Meetup 01052024.pdf
GenAI Risks & Security Meetup 01052024.pdf
 
TrustArc Webinar - Unlock the Power of AI-Driven Data Discovery
TrustArc Webinar - Unlock the Power of AI-Driven Data DiscoveryTrustArc Webinar - Unlock the Power of AI-Driven Data Discovery
TrustArc Webinar - Unlock the Power of AI-Driven Data Discovery
 
[2024]Digital Global Overview Report 2024 Meltwater.pdf
[2024]Digital Global Overview Report 2024 Meltwater.pdf[2024]Digital Global Overview Report 2024 Meltwater.pdf
[2024]Digital Global Overview Report 2024 Meltwater.pdf
 
Boost Fertility New Invention Ups Success Rates.pdf
Boost Fertility New Invention Ups Success Rates.pdfBoost Fertility New Invention Ups Success Rates.pdf
Boost Fertility New Invention Ups Success Rates.pdf
 
Developing An App To Navigate The Roads of Brazil
Developing An App To Navigate The Roads of BrazilDeveloping An App To Navigate The Roads of Brazil
Developing An App To Navigate The Roads of Brazil
 
presentation ICT roal in 21st century education
presentation ICT roal in 21st century educationpresentation ICT roal in 21st century education
presentation ICT roal in 21st century education
 
Real Time Object Detection Using Open CV
Real Time Object Detection Using Open CVReal Time Object Detection Using Open CV
Real Time Object Detection Using Open CV
 

[2019-07] GraphQL in depth (serverside)

  • 2. ➢ Just a query language GraphQL is … type User { id: ID! name: String! } type Query { user(id: ID): User } query { user(id: "10") { id name } } { "data": { "user": { "id": "10", "name": "Croquis" } } } +
  • 3. ➢ So, following is only for GraphQL.js. GraphQL is …
  • 4. import { GraphQLObjectType, GraphQLSchema, GraphQLString, printSchema } from 'graphql'; const schema = new GraphQLSchema({ query: new GraphQLObjectType({ name: 'RootQueryType', fields: { hello: { type: GraphQLString, }, }, }), }); console.log(printSchema(schema)); Define Schema: raw object schema { query: RootQueryType } type RootQueryType { hello: String }
  • 5. import { GraphQLList, GraphQLNonNull, GraphQLObjectType, GraphQLSchema, GraphQLString } from 'graphql'; const user = new GraphQLObjectType({ name: 'User', fields: { name: { type: GraphQLNonNull(GraphQLString), } } }); const schema = new GraphQLSchema({ query: new GraphQLObjectType({ name: 'Query', fields: { users: { type: GraphQLNonNull(GraphQLList(GraphQLNonNull(user))), } } }) }); Define Schema: custom type type Query { users: [User!]! } type User { name: String! }
  • 6. import { buildSchema } from 'graphql'; const schema = buildSchema(` type User { name: String! } type Query { users: [User!]! } `); Define Schema: using GraphQL schema language
  • 7. import 'reflect-metadata'; import { buildSchemaSync, Field, ObjectType, Query, Resolver } from 'type-graphql'; @ObjectType() class User { @Field() name: string; } @Resolver() class UserResolver { @Query((returns) => [User]) users() { return []; } } const schema = buildSchemaSync({ resolvers: [UserResolver], }); Define Schema: TypeGraphQL
  • 8. import { graphql, GraphQLObjectType, GraphQLSchema, GraphQLString } from 'graphql'; const schema = new GraphQLSchema({ query: new GraphQLObjectType({ name: 'RootQueryType', fields: { hello: { type: GraphQLString, resolve: () => 'World', }, }, }), }); console.log(await graphql(schema, '{ hello }’)); // { data: { hello: 'World' } } Execute Query
  • 9. // from graphql-express import { Source, parse, execute } from 'graphql'; function graphqlHTTP(options: Options): Middleware { return function graphqlMiddleware(request: $Request, response: $Response) { return getGraphQLParams(request) .then(() => { const source = new Source(query, 'GraphQL request'); documentAST = parse(source); return execute({ schema, documentAST, rootValue, context, variables, operationName }); }) .then(result => { if (response.statusCode === 200 && result && !result.data) { response.statusCode = 500; } const payload = JSON.stringify(result, null, pretty ? 2 : 0); sendResponse(response, 'application/json', payload); }); }; } Transport Layer
  • 10. ➢ All other things are how to resolve values Resolve
  • 11. import { graphql, GraphQLObjectType, GraphQLSchema, GraphQLString } from 'graphql'; const schema = new GraphQLSchema({ query: new GraphQLObjectType({ name: 'RootQueryType', fields: { hello: { type: GraphQLString, resolve: () => 'World', }, }, }), }); console.log(await graphql(schema, '{ hello }')); Basic Resolver
  • 12. import { buildSchema, graphql } from 'graphql'; const schema = buildSchema(` type Query { hello: String } `); console.log(await graphql(schema, '{ hello }', { hello: () => 'World', })); Root Value
  • 13. var defaultFieldResolver = function defaultFieldResolver(source, args, contextValue, info) { // ensure source is a value for which property access is acceptable. if (_typeof(source) === 'object' || typeof source === 'function') { var property = source[info.fieldName]; if (typeof property === 'function') { return source[info.fieldName](args, contextValue, info); } return property; } }; Root Value: internal
  • 14. import { buildSchema, graphql } from 'graphql'; const schema = buildSchema(` type Query { hello: String } `); schema.getQueryType()!.getFields().hello.resolve = () => { return 'World'; }; console.log(await graphql(schema, '{ hello }')); Attach Resolver After Creation
  • 15. import { buildSchema, graphql } from 'graphql'; import { addResolveFunctionsToSchema } from 'graphql-tools'; const schema = buildSchema(` type Query { hello: String } `); const resolvers = { Query: { hello: () => { return 'World'; } } }; addResolveFunctionsToSchema({ schema, resolvers }); console.log(await graphql(schema, '{ hello }')); Attach Resolver After Creation: graphql-tools // OR const resolvers = { Query: { hello: { type: GraphQLNonNull(GraphQLString), resolve() { return 'World'; }, } } };
  • 16. ➢ Function signature: resolver(obj, args, context, info) ✓ For root value: (args, context, info) ➢ Call defaultFieldResolver if not defined ➢ Always called even if object has a field value Resolver
  • 17. const schema = buildSchema(` type User { name: String! } type Query { users: [User!]! }`); const resolvers = { Query: { users(obj) { return [{ name: 'Name1' }, { name: 'Name2' }]; } }, User: { name(obj) { return obj.name.toLowerCase(); } } }; console.log(await graphql(schema, '{ users { name } }', { foo: 1 })); // { data: { users: [ { name: 'name1' }, { name: 'name2' } ] } } Resolver when object has a field value
  • 18. ➢ Contains the result returned from the resolver on the parent field ✓ Root Value for Query field ➢ @Root() in TypeGraphQL Resolver Arguments: 1) obj (source, parent)
  • 19. const schema = buildSchema(` type User { name: String! } type Query { users: [User!]! }`); const resolvers = { Query: { users(obj) { // obj === {foo: 1} return [{ id: 1 }, { id: 2 }]; } }, User: { name(obj) { /// obj === {id: 1} or {id: 2} return `Name${obj.id}`; } } }; console.log(await graphql(schema, '{ users { name } }', { foo: 1 })); // { data: { users: [ { name: 'Name1' }, { name: 'Name2' } ] } } Resolver Arguments: 1) obj (source, parent)
  • 20. ➢ An object with the arguments passed into the field in the query ➢ @Arg() or @Args() in TypeGraphQL Resolver Arguments: 2) args
  • 21. const schema = buildSchema(` type User { name(length: Int): String! } type Query { users(query: String, length: Int): [User!]! }`); const resolvers = { Query: { users(obj, args) { // args === { query: 'n', length: 2 } return [{ name: 'Daniel' }, { name: 'Johnny' }]; } }, User: { name(obj, args) { // args === { length: 3 } return obj.name.substr(0, args.length); } } }; console.log(await graphql(schema, '{ users(query: "N", length: 2) { name(length: 3) } }')); // { data: { users: [ { name: 'Dan' }, { name: 'Joh' } ] } } Resolver Arguments: 2) args
  • 22. ➢ An object shared by all resolvers in a particular query ➢ Per-request ➢ For examples: ✓ authentication information ✓ dataloader instances ➢ @Ctx() in TypeGraphQL Resolver Arguments: 3) context
  • 23. const schema = buildSchema(` type User { name: String! } type Query { users: [User!]! }`); const resolvers = { Query: { users(obj, args, context) { // context === { foo: 1 } context.bar = 1; return [{ name: 'Name1' }, { name: 'Name2' }]; } }, User: { name(obj, args, context) { // context === { foo: 1, bar: 1 } or { foo: 1, bar: 2 } context.bar++; return obj.name; } } }; console.log(await graphql(schema, '{ users { name } }', null, { foo: 1 })); // { data: { users: [ { name: 'Name1' }, { name: 'Name2' } ] } } Resolver Arguments: 3) context
  • 24. ➢ Contains information about the execution state of the query ➢ @Info in TypeGraphQL ➢ Some utilities: graphql-list-fields, graphql-fields-list, @croquiscom/crary-graphql Resolver Arguments: 4) info export interface GraphQLResolveInfo { readonly fieldName: string; readonly fieldNodes: ReadonlyArray<FieldNode>; readonly returnType: GraphQLOutputType; readonly parentType: GraphQLObjectType; readonly path: ResponsePath; readonly schema: GraphQLSchema; readonly fragments: { [key: string]: FragmentDefinitionNode }; readonly rootValue: any; readonly operation: OperationDefinitionNode; readonly variableValues: { [variableName: string]: any }; }
  • 25. const resolvers = { Query: { users(obj, args, context, info) { // info.fieldName === 'users' // print(info.fieldNodes[0]) === 'users { name }' // info.parentType === Query // info.returnType === [User!]! return [{ name: 'Name1' }, { name: 'Name2' }]; } }, User: { name(obj, args, context, info) { // info.fieldName === 'name' // print(info.fieldNodes[0]) === 'name' // info.parentType === User // info.returnType === String! return obj.name; } } }; Resolver Arguments: 4) info
  • 26. import { addArgumentToInfo, getFieldList, getFieldList1st, getFieldString } from '@croquiscom/crary-graphql'; const schema = buildSchema(` type Name { first: String, last: String } type User { name: Name, age: Int } type Query { users: [User!]! }`); const resolvers = { Query: { users(obj, args, context, info) { // getFieldList(info) === [ 'name.first', 'name.last', 'age' ] // getFieldList1st(info)) === [ 'name', 'age' ] // getFieldString(info) === 'name { first last } age' // print(info.fieldNodes[0]) === 'users { name { first last } age }' // info.variableValues === {} const newInfo = addArgumentToInfo(info, 'length', 3, GraphQLInt); // print(newInfo.fieldNodes[0]) === 'users(length: $_c_length) { name { first last } age }' // newInfo.variableValues === { _c_length: 3 } return []; } } }; console.log(await graphql(schema, '{ users { name { first last } age } }')); Resolver Arguments: 4) info - @croquiscom/crary-graphql
  • 27. ➢ buildSchema + addResolveFunctionsToSchema Advanced: makeExecutableSchema const typeDefs = ` type User { name: String! } type Query { users: [User!]! }`; const resolvers = { Query: { users() { return [{ name: 'Name1' }, { name: 'Name2' }]; } }, User: { name(obj) { return obj.name; } } }; const schema = makeExecutableSchema({ typeDefs, resolvers }); console.log(await graphql(schema, '{ users { name } }'));
  • 28. ➢ Generate GraphQL schema objects that delegate to a remote server Advanced: makeRemoteExecutableSchema import { HttpLink } from 'apollo-link-http'; import { importSchema } from 'graphql-import'; import { makeRemoteExecutableSchema } from 'graphql-tools'; import fetch from 'node-fetch'; const schema = makeRemoteExecutableSchema({ schema: importSchema(`${__dirname}/schema.graphql`), link: new HttpLink({ uri: 'http://other.service/graphql', fetch: fetch, }), });
  • 29. export default function linkToFetcher(link: ApolloLink): Fetcher { return (fetcherOperation: FetcherOperation) => { return makePromise(execute(link, fetcherOperation as GraphQLRequest)); }; } export function createResolver(fetcher: Fetcher): GraphQLFieldResolver<any, any> { return async (root, args, context, info) => { const fragments = Object.keys(info.fragments).map( fragment => info.fragments[fragment], ); const document = { kind: Kind.DOCUMENT, definitions: [info.operation, ...fragments], }; const result = await fetcher({ query: document, variables: info.variableValues, context: { graphqlContext: context }, }); return checkResultAndHandleErrors(result, info); }; } Advanced: makeRemoteExecutableSchema internal
  • 30. ➢ Combine multiple GraphQL schemas together Advanced: mergeSchemas const user_schema = buildSchema(` type User { id: ID!, name: String! } type Query { users: [User!]! }`); const post_schema = buildSchema(` type Post { contents: String! } type Query { posts(user_id: ID): [Post!]! }`); const schema = mergeSchemas({ schemas: [ user_schema, post_schema, `extend type User { posts: [Post!]! }`, ], }); type Post { contents: String! } type Query { users: [User!]! posts(user_id: ID): [Post!]! } type User { id: ID! name: String! posts: [Post!]! }
  • 31. User: { posts: { fragment: '... on User { id }', resolve(obj, args, context, info) { console.log(print(info.fieldNodes[0])); return info.mergeInfo.delegateToSchema({ schema: post_schema, operation: 'query', fieldName: 'posts', args: { user_id: obj.id }, context, info, }); } } }
 // for query '{ users { name posts { contents } } }' // 1. 'users { name ... on User { id } }' on user // 2. 'posts { contents }' on merged // 3. 'posts(user_id: $_v0_user_id) { contents }' on post Advanced: mergeSchemas + delegateToSchema
  • 32. ➢ Merged schema must have all operations for delegateToSchema Advanced: manual delegation resolve(obj, args, context, info) { info = transformInfoForSchema(info, post_schema, info.mergeInfo.fragments); const fields = getFieldString(info); const query = `query($user_id: ID) { posts(user_id: $user_id) { ${fields} } }`; const variables = { user_id: obj.id }; return fetch('http://post/graphql', { method: 'POST', body: JSON.stringify({ query, variables }), headers: { 'Content-Type': 'application/json', }, }); },
  • 33. directive @authorized on FIELD_DEFINITION type Query { posts: [Post!]! @authorized } import { SchemaDirectiveVisitor } from 'graphql-tools'; class AuthorizedDirective extends SchemaDirectiveVisitor { visitFieldDefinition(field: GraphQLField<any, any>) { const { resolve = defaultFieldResolver } = field; field.resolve = async (source, args, context, info) => { checkAuthorized(context); const result = await resolve(source, args, context, info); return result; }; } } SchemaDirectiveVisitor.visitSchemaDirectives(schema, { authorized: AuthorizedDirective }); Advanced: directive
  • 34. ➢ Use DataLoader ➢ Batch function must return an array of values that have same order to an array of keys ➢ It is common to create a new DataLoader per request (context will be helpful) Advanced: N+1 problem import DataLoader from 'dataloader'; resolve(parent, args, context, info) { if (!context.loader) { context.loader = new DataLoader((keys) => { return ['value for keys[0]', 'value for keys[1]', ...]; }); } return context.loader.load(key); },
  • 35. ➢ Subscription is a one-way communication from server to client. ✓ Socket.io is a two-way ➢ You need to implement ‘subscribe’ method that returns AsyncIterator Advanced: subscription
  • 36. import { PubSub } from 'graphql-subscriptions'; const pubSub = new PubSub(); async function doTask(task_id: number) { for (let i = 0; i < 10; i++) { await Bluebird.delay(1000); pubSub.publish(`doTask-${task_id}`, i); } pubSub.publish(`doTask-${task_id}`, 'done'); } const schema = new GraphQLSchema({ subscription: new GraphQLObjectType({ name: 'RootSubscriptionType', fields: { doTaskProgressed: { type: GraphQLString, args: { task_id: { type: new GraphQLNonNull(GraphQLInt) } }, subscribe: (source, args) => { const task_id = args.task_id; return pubSub.asyncIterator(`doTask-${task_id}`); }, } } }) }); Advanced: subscription - run task in separate thread - Task will run regardless subscription - You need ‘done’ to notify task ended
  • 37. const schema = new GraphQLSchema({ subscription: new GraphQLObjectType({ name: 'RootSubscriptionType', fields: { doTaskWithProgress: { type: GraphQLString, async *subscribe(source, args) { for (let i = 0; i < 10; i++) { await Bluebird.delay(1000); yield i; } }, } } }) }); Advanced: subscription - run task in subscribe - Task will stop when subscription ended - Subscription will be ended when task ended
  • 38. ➢ graphql-resolvers ✓ combineResolvers, pipeResolvers, allResolvers ✓ resolveDependee, resolveDependees, isDependee ➢ graphql-scalars ✓ DateTime, EmailAddress, NegativeInt, PostalCode, URL, … ➢ Apollo Federation ✓ Replace schema stitching (remote + merge) Advanced: other packages
  • 39. ➢ GraphQL Resolvers: Best Practices ➢ Further Reading
  • 40. - Thank you for your attention