GraphQL the holy contract between client and server @nodkz Pavel Chertorogov with GraphQL since 2015 3

Client-server intro 4 Intro Client-server apps

Redis HTML XML PostgreSQL REST HTTP ES URL JSON MongoDB form-data WS etc… MySQL url-encoded etc… etc… SERVER CLIENTS

Java Python iOS .NET NodeJS Go Browsers Android etc… C# Ruby etc… 5 Intro Backend capabilities

Invoice Customer

id id 1 customer_id * name amount date

Simple DB Schema Diagram 6 Intro

Wordpress 4.4.2 Schema (12 tables)

Drupal8 Schema (60 tables) SalesLogix Schema (168 tables) 7 Intro

… and even more Monster with 333 tables Order table (138 fields)

Hi-Res: http://anna.voelkl.at/wp-content/uploads/2016/12/ce2.1.3.png Magento: Commerce Solutions for Selling Online (333 tables) 8 Intro

How to create API for this db-schema HELL? 9 Intro

Wait! I have a better plan! 10 GraphQL Basics This is GraphQL Schema

Wikipedia MBT-70 schema 11

GraphQL basics 12 GraphQL Basics GraphQL – is a …

query executor + C# for on Schema .NET Python NodeJS for Frontenders for Backenders Ruby Go etc… 13 GraphQL Basics GraphQL Query Language

GraphQL query Response in JSON 14 GraphQL Basics GraphQL Query Language

GraphQL query Response in JSON 15 GraphQL Basics GraphQL Schema

GraphQL Schema Single entrypoint

Query Mutation Subscription Type Type Type

READ (stateless) WRITE (stateless) EVENTS (stateful) 16 GraphQL Basics ObjectType

Type Type Invoice Name

Name id Field

Field 1 customerId Field amount Field Field N date Field 17 GraphQL Basics Field Config

Type

Field 1

Field N

Type Scalar or Object Type which returns resolve

Resolve Function with fetch logic from any data source

Args Set of input args for resolve function

Description Documentation

DeprecationReason Field hiding 18 GraphQL Basics Relations between types

Type Type Invoice Customer

Field id Int * 1 id id Field Int customerId Field Field String Int name Field amount Decimal date Field Date 19 GraphQL Basics

One does not simply draw a relation line in GraphQL!

Invoice * 1 Customer 20 GraphQL Basics Relation is a new field in your Type

Type Type Invoice Customer

id Field Field select * from invoice where id customerId Field customer_id = {customer.id} name Field Field amount Resolve invoices Field Array date Field

customer Field Resolve Customer select * from customer where id = {invoice.customerId} 21 GraphQL Basics — Limit? Offset? Sort? Additional filtering? — No problem, add them to args and process in resolve function!

Type Type Invoice Customer

select * from invoice where id Field Field customer_id = {customer.id} id customerId Field limit {args.limit} name Field Field amount Resolve invoices Field Args Array date Field Limit: 3 22 GraphQL Basics Resolve function

function (source, args, context, info ) { // access logic (check permissions) // fetch data logic (from any mix of DBs) // processing logic (operations, calcs) return data; } ANY PRIVATE BUSINESS LOGIC… 23 GraphQL Basics Schema Introspection

printSchema(schema); // txt output (SDL format)

graphql(schema, introspectionQuery); // json output (AST)

Just remove Resolve functions (private business logic) and you get PUBLIC schema 24 GraphQL Basics Schema Introspection example SDL format types type Customer { • id: Int (txt) • fields name: String args # List of Invoices for current Customer • invoices(limit: Int): [Invoice] • docs } resolve • # Show me the money • directives type Invoice { input types id: Int • customerId: Int • enums amount: Decimal # Customer data for current Invoice • interfaces customer: Customer • unions oldField: Int @deprecated(reason: "will be removed") } 25 GraphQL Basics Schema Introspection provides an ability for awesome tooling: • Autocompletion • Query validation • Documentation • Visualization • TypeDefs generation for static analysis (Flow, TypeScript)

GraphiQL — graphical interactive in-browser GraphQL IDE Eslint-plugin-graphql — check queries in your editor, CI Relay-compiler — generates type definitions from queries 26 GraphQL Basics Type definition example

const QueryType = new GraphQLObjectType({ name: 'Query', fields: () => ({ films: { type: new GraphQLList(FilmType), args: { limit: { type: GraphQLInt, defaultValue: 5 }, }, resolve: async (source, args) => { const data = await loadData(`https://swapi.co/api/films/`); return data.slice(0, args.limit); }, }, ...otherFields, }), }); 27 GraphQL Basics Type definition example

const QueryTypeQuery = new GraphQLObjectType({ name: 'Query',Type Type fields: () => ({ Field 1 filmsfield : { typetype : new GraphQLList(FilmType), Field N argsargs : { limit: { type: GraphQLInt, defaultValue: 5 }, Type }, Args resolveresolve : async (source, args) => { const data = await loadData(`...`); Resolve return data.slice(0, args.limit); }, Description }, FieldConfig ...otherFields, DeprecationReason }), }); 28 GraphQL Basics Don’t forget to read about • input types • directives • enums • interfaces • unions • fragments http://graphql.org/learn/ 29 Backend capabilities Client requirements https://graphql-compose.herokuapp.com/ GraphQL Demo 30 Fetch Network sub-fetch 1 Network RESULT Delay Delay sub-fetch 2 REST API sub-fetch N - Sub-fetch logic on client side (increase bundle size) - Over-fetching (redundant data transfer/parsing)

Query Network RESULT Delay

GraphQL + No additional network round-trip (speed) + Exactly requested fields (speed) + Sub-fetch logic implemented on server side 31 Intro A copy from one of the previous slides… Client-server apps

Redis HTML XML PostgreSQL REST HTTP ES URL JSON MongoDB form-data WS etc… MySQL url-encoded etc… etc… SERVER CLIENTS

Java Python iOS .NET NodeJS Go Browsers Android etc… C# Ruby etc… 32 GraphQL – is a query language for APIs

Specification for describing the capabilities and requirements of data models Redis PostgreSQL for client‐server apps ES HTTP MongoDB WS MySQL SCHEMA Pretty QUERY INTROSPECTION etc… etc… Language

SERVER CLIENTS NodeJS Go .NET Browsers iOS Java Ruby C# Python etc… etc… Android 33 For frontend developers

Static Analysis 34 Static Analysis Static type checks � PRODUCTIVITY • Types checks • Functions call checks • Auto-suggestion • Holy refactoring

Static program analysis is the analysis of computer that is performed without actually executing programs 35 Static Analysis SERVER JS CLIENT

GraphQL File with GraphQL Query Schema and Response processing

Let’s turbo-charge our client apps static analysis with GraphQL queries 36 Static Analysis SERVER JS CLIENT

GraphQL File with GraphQL Query Schema and Response processing

1

Schema Introspection 37 Static Analysis SERVER JS CLIENT

GraphQL File with GraphQL Query Schema and Response processing

1

Schema 2 Introspection Relay-compiler Watcher Apollo-codegen 38 Static Analysis SERVER JS CLIENT

GraphQL File with GraphQL Query Schema and Response processing

1 File with Response Type Defs Schema 2 3 Introspection Watcher

Hey, you have a wrong Query 39 Static Analysis SERVER JS CLIENT

GraphQL File with GraphQL Query Schema and Response processing 4 1 File with Response Type Defs Schema 2 3 Introspection Watcher 40 Static Analysis SERVER JS CLIENT

GraphQL File with GraphQL Query Schema and Response processing 4 1 File with Response Type Defs Schema 2 3 5 Introspection Watcher Flowtype or TypeScript 41 Static Analysis SERVER JS CLIENT

GraphQL File with GraphQL Query Schema and Response processing 4 1 File with Response Type Defs Schema 2 3 5 Introspection Watcher Flowtype

Houston, we have a Type Check problem 6 at line 19287 col 5: possible undefined value 42 Static Analysis DEMO GraphQL Query Generated Response Type Def Crappy Code Flow typed Code Flow error 43 Balance.js Static Analysis

GraphQL Query import { graphql } from 'react-relay'; const query = graphql` query BalanceQuery { viewer { JS CLIENT

cabinet { File with GraphQL Query accountBalance and Response processing 4 } File with Response Type Defs } 3 5 }`; Watcher Flowtype 44 __generated__/BalanceQuery.graphql.js Static Analysis

Generated Response Type Def JS CLIENT File with GraphQL Query and Response processing /* @flow */ 4

File with /*:: Response Type Defs export type BalanceQueryResponse = {| 3 5 +viewer: ?{| Watcher Flowtype +cabinet: ?{| +accountBalance: ?number; |}; Writer time: 0.53s [0.37s compiling, …] |}; Created: - BalanceQuery.graphql.js |}; Unchanged: 291 files */ Written default in 0.61s 45 Balance.js Static Analysis import { graphql } from 'react-relay'; Crappy Code import * as React from 'react';

export default class Balance extends React.Component { render() { const { viewer } = this.props; return

Balance {viewer.cabinet.accountBalance} JS CLIENT

File with

; GraphQL Query and Response processing } 4 } File with Response Type Defs const query = graphql`query BalanceQuery { 3 5 viewer { cabinet { accountBalance } } Watcher Flowtype }`; 46 Balance.js Static Analysis

Flow typed Code import { graphql } from 'react-relay'; import * as React from 'react'; import type { BalanceQueryResponse } from './__generated__/BalanceQuery.graphql';

type Props = BalanceQueryResponse;

export default class Balance extends React.Component { render() { JS CLIENT const { viewer } = this.props; File with GraphQL Query return

Balance {viewer.cabinet.accountBalance}
; and Response processing } 4

} File with const query = graphql`query BalanceQuery { Response Type Defs viewer { cabinet { accountBalance } } 3 5 }`; Watcher Flowtype

47 Balance.js Static Analysis

/* @flow */ Flow typed Code import { graphql } from 'react-relay'; import * as React from 'react'; import type { BalanceQueryResponse } from './__generated__/BalanceQuery.graphql';

type Props = BalanceQueryResponse;

export default class Balance extends React.Component { render() { JS CLIENT const { viewer } = this.props; File with GraphQL Query return

Balance {viewer.cabinet.accountBalance}
; and Response processing } 4

} File with const query = graphql`query BalanceQuery { Response Type Defs viewer { cabinet { accountBalance } } 3 5 }`; Watcher Flowtype

48 Static Analysis Flow errors

Error: src/_demo/Balance.js:11

11: return

Your balance: {viewer.cabinet.accountBalance}
; ^^^^^^^ property `cabinet`. Property cannot be accessed on possibly null value 11: return
Your balance: {viewer.cabinet.accountBalance}
; ^^^^^^ null

Error: src/_demo/Balance.js:11 11: return

Your balance: {viewer.cabinet.accountBalance}
; ^^^^^^^ property `cabinet`. Property cannot be accessed on possibly undefined value 11: return
Your balance: {viewer.cabinet.accountBalance}
; ^^^^^^ undefined 49 Static Analysis Flow errors for missing field

type Props = BalanceQueryResponse; class Balance extends React.Component { render() { const { viewer } = this.props; return

{viewer.invoices}
; } } 50 Static Analysis Flow errors for missing field

Error: src/_demo/Balance.js:11 11: return

{viewer.invoices}
; ^^^^^^^^ property `invoices`. Property not found in v- 13: +viewer: ?{| 14: +cabinet: ?{| 15: +accountBalance: ?number; 16: |}; 17: |}; -^ object type. See: src/_demo/__generated__/ BalanceQuery.graphql.js:13 51 For backend developers GraphQL Query Problems 52 GraphQL Query Problems

Denial of Service attacks aka Resource exhaustion attaks Solutions: query HugeResponse { user { • avoid nesting relations friends(limit: 1000) { friends(limit: 1000) { cost analysis on the query friends(limit: 1000) { • ... } pre-approve queries that } • } the server can execute } (persisted queries by unique ID) } using by 53 GraphQL Query Problems

N+1 query problem Solution: DataLoader query NestedQueryN1 { { const CatLoader = new DataLoader( productList { ids => Category.findByIds(ids) id ); categoryId 1 query for category { ProductList CatLoader.load(1); id CatLoader.load(2); name CatLoader.load(1); } N queries CatLoader.load(4); } for fetching every } will do just one BATCH request } Category by id on next tick to the Database 54 For backend developers Schema construction problems 55 Query Type example Problem #1: too much copy/paste const QueryType = new GraphQLObjectType({ name: 'Query', fields: () => ({ films: ..., 6 fields and persons: ..., every FieldConfig planets: ..., species: ..., consists from starships: ..., almost identical vehicles: ..., 12 ctrl+c/ctrl+v lines }), The Star Wars API }); https://swapi.co 56 Problem #1: too much copy/paste FieldConfig example for films field films: { type: new GraphQLList(FilmType), args: { limit: { type: GraphQLInt, defaultValue: 5 } }, resolve: async (source, args) => { const data = await loadData(`https://swapi.co/api/films/`); if (args && args.limit > 0) { return data.slice(0, args.limit); } return data; }, 57 Comparison of two FieldConfigs Problem #1: too much copy/paste

{ films: { type: new GraphQLList(FilmType), args: { limit: { type: GraphQLInt, defaultValue: 5 } }, resolve: async (source, args) => { const data = await loadData(`https://swapi.co/api/films/`); if (args && args.limit > 0) { return data.slice(0, args.limit); } return data; }, }, differs planets: { type: new GraphQLList(PlanetType), args: { limit: { type: GraphQLInt, defaultValue: 5 } }, only by url resolve: async (source, args) => { const data = await loadData(`https://swapi.co/api/planets/`); if (args && args.limit > 0) { return data.slice(0, args.limit); } return data; }, }, } 58 Problem #1: too much copy/paste Solution 1: you may generate your resolve functions

function createListResolve(url) { return async (source, args) => { const data = await loadData(url); if (args && args.limit > 0) { return data.slice(0, args.limit); } return data; }; create a function } which returns a resolve function 59 Problem #1: too much copy/paste Solution 1: you may generate your resolve functions

{ films: { type: new GraphQLList(FilmType), films: { args: { limit: { type: GraphQLInt, defaultValue: 5 } }, resolve: async (source, args) => { type: new GraphQLList(FilmType), const data = await loadData(`https://swapi.co/api/films/`); args: { limit: { type: GraphQLInt, defaultValue: 5 } }, if (args && args.limit > 0) { return data.slice(0, args.limit); resolve: createListResolve(`https://swapi.co/api/films/`), } }, return data; }, }, reduce N times 7 LoC to 1 LoC

planets: { type: new GraphQLList(PlanetType), args: { limit: { type: GraphQLInt, defaultValue: 5 } }, planets: { resolve: async (source, args) => { type: new GraphQLList(PlanetType), const data = await loadData(`https://swapi.co/api/planets/`); if (args && args.limit > 0) { args: { limit: { type: GraphQLInt, defaultValue: 5 } }, return data.slice(0, args.limit); resolve: createListResolve(`https://swapi.co/api/planets/`), } return data; }, }, }, } 60 Problem #1: too much copy/paste Solution 2: you may generate your FieldConfigs

films: { type: new GraphQLList(FilmType), args: { limit: { type: GraphQLInt, defaultValue: 5 } }, resolve: createListResolve(`https://swapi.co/api/films/`), }, differs only by `Type` and `url`

planets: { type: new GraphQLList(PlanetType), args: { limit: { type: GraphQLInt, defaultValue: 5 } }, resolve: createListResolve(`https://swapi.co/api/planets/`), }, 61 Problem #1: too much copy/paste Solution 2: you may generate your FieldConfigs

function createFieldConfigForList(type, url) { return { type: new GraphQLList(type), args: { limit: { type: GraphQLInt, defaultValue: 5 } }, resolve: createListResolve(url), }; } create a function which returns a FieldConfig 62 Problem #1: too much copy/paste Solution 2: you may generate your FieldConfigs

films: { type: new GraphQLList(PlanetType), args: { limit: { type: GraphQLInt, defaultValue: 5 } }, resolve: createListResolve(`https://swapi.co/api/films/`), }, planets: { type: new GraphQLList(FilmType), args: { limit: { type: GraphQLInt, defaultValue: 5 } }, resolve: createListResolve(`https://swapi.co/api/planets/`), }, 10 LoC reduced to 2 LoC

{ films: createFieldConfigForList(FilmType, `https://swapi.co/api/films/`), planets: createFieldConfigForList(PlanetType, `https://swapi.co/api/planets/`), } 63 Problem #1: too much copy/paste Solution 1: you may generate your resolve functions Solution 2: you may generate your FieldConfigs

was reduced in 3 times 30 LoC

DRY principle 90 LoC (don't repeat yourself) 64 Problem #2: keep GraphQL types in sync with ORM/DB How to keep to Schemas in SYNC?

Model: User Type: User

SYNC

• add new fields With time you may: • change field types • remove fields • rename fields 65 Problem #2: keep GraphQL types in sync with ORM/DB Solution: generate GraphQL types from ORM models

Model: User Type: User

GENERATE

• via some cli/script • on server boot load (better) SSOT principle (single source of truth) 66 Problem #3: mess in types and resolve functions

InvoiceType.js CustomerType.js

id id customerId name amount Resolve invoices date

customer Resolve

select * from CUSTOMER select * from INVOICE where … where … InvoiceType.js contains CUSTOMER query CustomerType.js contains INVOICE query 67 Problem #3: mess in types and resolve functions

CustomerType.js id select * from INVOICES where … name select * from TRANSACTIONS where … invoices transactions select * from TICKETS where … tickets select * from EVENTS where … events select * from LIKES where … likes select * from MESSAGES where … messages select * from … …

CustomerType.js knows too much about queries of others types 68 Problem #3: mess in types and resolve functions

Customer AdminNoteType.js Resolve

id Resolve Product msg

Resolve Invoice

Resolve …

What if you need to restrict access for some group of users? Modify resolvers in all places? 69 Problem #3: mess in types and resolve functions Solution: GraphQL Models*

* “GraphQL Model” is not a part of GraphQL specification.

It’s suggested additional layer of abstraction for more comfortable way to construct and maintain your schema and relations into it. 70 Problem #3: mess in types and resolve functions Solution: GraphQL Models Contains:

class CustomerGQLModel { 1. type definition type: CustomerGraphQLType; 1 Type resolvers: { 2. all possible findById: { type: CustomerGraphQLType, 2 MAP ways to CRUD args: { id: 'Int!' }, data resolve: (_, args) => load(`select * from customer where id = ${args.id}`), }, 3. may have other findMany: { ... }, helper methods createOne: { ... }, updateOne: { ... }, and data removeOne: { ... }, ... }; inputType: CustomerGraphQLInputType; InputType } 3

71 Problem #4: import { makeExecutableSchema } from ‘graphql-tools'; Writing Types via SDL and providing resolvers separately.

const typeDefs = ` type args const resolvers = { resolve type Query { Query: { customer(id: Int!): Customer customer: (_, { id }) => invoices(limit: Int): [Invoice] Customer.find({ id: id }), } invoices: (_, { limit }) => Invoice.findMany({ limit }),

}, type Customer { Customer: { id: Int! invoices: (source) => firstName: String Invoice.find({ customerId: source.id }), invoices: [Invoice] }, }`; }; const schema = makeExecutableSchema({ typeDefs, resolvers }); It’s nice developer experience for small to medium sized schema BUT… 72 Problem #4: import { makeExecutableSchema } from ‘graphql-tools'; Hard to work with complex input args type Query { All highlighted parts with red lines should be in sync invoices(filter: FilterInput): [Invoice] } invoices: (_, { filter }) => { const { num, dateRange, status } = filter; input FilterInput { num: Int const q = {}; dateRange: DateRangeInput if (num) q.num = num; status: InvoiceStatusEnum } if (dateRange) q['date.$inRange'] = dateRange; input DateRangeInput { if (status) q.status = status; min: Date max: Date return Post.findMany(q); } }, enum InvoiceStatusEnum { • If one InputType used in several resolvers, then the unpaid paid declined complexity of refactoring increases dramatically. } • If one InputType per resolver, then too much * This example contains an error in the code, try to find it ;) copy/paste almost similar types. 73 Problem #4: import { makeExecutableSchema } from ‘graphql-tools'; Solution: build the schema programmatically Generate FieldConfigs via your custom functions (Resolvers) …

class InvoiceGQLModel { findManyResolver(configOptions) { return { type: InvoiceType, type args: { args filter: { type: new GraphQLInputObjectType({ … })}, resolve }, resolve: (_, args) => Invoice.findMany(args), } } findByIdResolver() { … } … … and then … 74 Problem #4: import { makeExecutableSchema } from ‘graphql-tools'; Solution: build the schema programmatically …and then build your Schema from fields and your Resolvers

import { GraphQLSchema, GraphQLObjectType } from 'graphql'; import InvoiceResolvers from './InvoiceResolvers';

const schema = new GraphQLSchema({ query: new GraphQLObjectType({ name: 'Query', fields: { invoices: InvoiceResolvers.findManyResolver(), ... }, }), }); http://graphql.org/graphql-js/constructing-types/ 75 Problem #4: import { makeExecutableSchema } from ‘graphql-tools';

type args type Query { resolve invoices(filter: FilterInput): [Invoice] invoices: (_, { filter }) => { … } }

combine code from different places back to FieldConfig { type type: new GraphQLList(Invoice), args: { args filter: { type: new GraphQLInputObjectType({ … })}, resolve }, resolve: (_, { filter }) => { … }, } 76 Problem #4: import { makeExecutableSchema } from ‘graphql-tools';

type Query { invoices(filter: FilterInput): [Invoice] invoices: (_, { filter }) => { … } }

combine code from different places back to FieldConfig { type: [Invoice], args: { filter: `input FilterInput { … }`, my code with }, graphql-compose resolve: (_, { filter }) => { … }, } 77 For backend developers

Graphql-compose packages 78 graphql-compose packages Graphql-compose-* — OSS packages family for generating GraphQL Types The main idea is to generate GraphQL Schema from your ORM/Mappings at the server startup with a small lines of code as possible.

MIT License Help wanted Exposes Flowtype/Typescript declarations With awful docs all packages have more than 460 starts on GitHub 79 graphql-compose packages

Graphql-compose works almost like a webpack

It bundles your Schema from different type sources 80 graphql-compose packages

ORM Schema, Schema creation workflow 1 Mapping Generate editable GraphQL Types with a set of CRUD Resolvers (FieldConfigs w/ args, type, resolve)

TypeComposer Manually created TypeComposers 2 with Resolvers or Vanilla GraphQL types Remove/add fields Wrap default Resolvers with custom business logic Create own Resolvers (FieldConfigs) Build relations between types 3 GraphQL Schema 81 graphql-compose packages Graphql-compose provides handy syntax for manual type creation

const InvoiceItemTC = TypeComposer.create(` type InvoiceItem { description: String qty: Int price: Float } `); SDL syntax for simple types (schema definition language) 82 graphql-compose packages Graphql-compose provides handy syntax for manual type creation

const InvoiceTC = TypeComposer.create({ name: 'Invoice', fields: { id: 'Int!', SDL syntax inside now: { type: 'Date', resolve: () => Date.now() Type as function, [ ] as List }, items: () => [InvoiceItemTC], }, Config Object Syntax }); for complex types 83 graphql-compose packages Graphql-compose provides methods for modifying Types

TC.addFields({ field1: …, field2: … }); TC.removeField(['field2', ‘field3']); TC.extendField('lat', { description: 'Latitude', resolve: () => {} });

TC.hasField('lon'); // boolean TC.getFieldNames(); // ['lon', 'lat'] TC.getField('lon'); // FieldConfig TC.getField('lon'); // return FieldConfig TC.getFields(); // { lon: FieldConfig, lat: FieldConfig } TC.setFields({ ... }); // completely replace all fields TOP 3 commonly TC.setField('lon', { ... }); // replace `lon` field with new FieldConfig TC.removeField('lon'); TC.removeOtherFields(['lon', 'lat']); // will remove all other fields used methods TC.reorderFields(['lat', 'lon']); // reorder fields, lat becomes first TC.deprecateFields({ 'lat': 'deprecation reason' }); // mark field as deprecated TC.getFieldType('lat'); // GraphQLFloat TC.getFieldTC('complexField'); // TypeComposer TC.getFieldArgs('lat'); // returns map of args config or empty {} if no args Bunch of other TC.hasFieldArg('lat', 'arg1'); // false TC.getFieldArg('lat', 'arg1'); // returns arg config useful methods 84 graphql-compose packages Graphql-compose create relations between Types via FieldConfig

Type as function InvoiceTC.addField('items', { solves hoisting problems type: () => ItemsTC, resolve: (source) => { return Items.find({ invoiceId: source.id }) }, }); 85 graphql-compose packages Graphql-compose create relations between Types via Resolvers

InvoiceTC.addRelation('items', { resolver: () => ItemsTC.getResolver('findMany'), prepareArgs: { filter: source => ({ invoiceId: source.id }), }, }); Prepare args for Resolver 86 graphql-compose packages Graphql-compose is a great tool for writing your own type generators/plugins

graphql-compose-json type generator graphql-compose-mongoose type generator resolver generator

graphql-compose-pagination resolver generator

graphql-compose-connection resolver generator

graphql-compose-relay type/resolver modifier

graphql-compose-elasticsearch type generator resolver generator http API wrapper � graphql-compose-aws SDK API wrapper 87 graphql-compose packages Huge GraphQL Schema example graphql-compose-aws ~700 lines of code, 2 days of work generates more than 10 000 GraphQL Types schema size ~2 Mb in SDL, ~9 Mb in json JUST IN 2 DAYS 88 graphql-compose packages AWS Cloud API in GraphQL Schema generation takes about ~1-2 seconds 125 Services 3857 Operations 6711 Input/Output params

https://graphqlbin.com/plqhO 89 graphql-compose packages

Graphql-compose schema demos

Mongoose, Elastic, Northwind https://github.com/nodkz/graphql-compose-examples https://graphql-compose.herokuapp.com

Wrapping REST API https://github.com/lyskos97/graphql-compose-swapi https://graphql-compose-swapi.herokuapp.com 90

Last words… 91 GraphQL is awesome!

less stress more success less time on coding less network traffic � less errors

PS. SOMETIMES A LOT LESS 92 Tutorials

Read medium graphql

Watch youtube graphql

Glue howtographql.com 93 Take away! GraphQL is powerful query language with great tools

GraphQL is typed so it helps with static analysis on clients

Generate GraphQL Schemas on server 94

THANKS! Pavel Chertorogov nodkz 95 GraphQL is a

for your server and client apps