Improving
GraphQL and Apollo Workflow
Eddy Nguyen


❤️

Overview
-
React, GraphQL and TypeScript
-
Simple example
-
Understanding how it works
-
Improving workflow
React
-
JavaScript UI framework
-
Awesome
GraphQL
-
Schema based query language for APIs
-
Client asks server for data it needs
-
Client libraries: Apollo Client, Relay, etc.
-
Server libraries: gqlgen, Apollo Server, etc.
TypeScript
-
Superset of JavaScript
-
Type checked at compile time
-
Generics
function foo<T>(...): T {...}
const result = foo<string>(...); // result is 'string'
const result = foo<number>(...); // result is 'number'// UserDisplay.tsx
import gql from 'graphql-tag';
import React from 'react';
import { useQuery } from '@apollo/react-hooks';
const GET_USER = gql`
query GetUser($id: ID!) {
user(id: $id) {
id
firstName
lastName
}
}
`;
function UserDisplay(){
const { loading, error, data } = useQuery(GET_USER, {
variables: { id: '1' }
});
...
}Example
console.log(GET_USER);
{
"kind":"Document",
"definitions":[{
"kind":"OperationDefinition",
"operation":"query",
"name":{ "kind":"Name", "value":"GetUser"},
"variableDefinitions":[{
"kind":"VariableDefinition",
"variable":{
"kind":"Variable", "name":{ "kind":"Name", "value":"id" }
},
"type":{ "kind":"NonNullType", "type":{
"kind":"NamedType", "name":{ "kind":"Name", "value":"ID" }
}},
"directives":[]
}],
"directives":[],
"selectionSet":{ "kind":"SelectionSet",
"selections":[{
"kind":"Field", "name":{"kind":"Name","value":"user"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}],"directives":[],
"selectionSet":{
"kind":"SelectionSet",
"selections": [
{"kind":"Field","name":{"kind":"Name","value":"id"},"arguments":[],"directives":[]},
{"kind":"Field","name":{"kind":"Name","value":"firstName"},"arguments":[],"directives":[]},
{"kind":"Field","name":{"kind":"Name","value":"lastName"},"arguments":[],"directives":[]}
]
}
}]
}
}]
};Abstract Syntax Tree (AST)
-
Describes GraphQL operations and fields
-
Document Node is the object representation
Generating document nodes
graphql-tag
+ Easy to use
- gql sent to client
- Doc parsed at runtime
Webpack loader
+ Compiled on server
- Need webpack
Generating document nodes
graphql-tag
+ Easy to use
- gql sent to client
- Doc parsed at runtime
Pure document node
+ No external libraries
- Impossible to write
Generating document nodes
Webpack loader
+ Compiled on server
- Need webpack
graphql-tag
+ Easy to use
- gql sent to client
- Doc parsed at runtime
Simple ways to improve
-
Webpack loader
-
Typescript
-
Abstract logic
Webpack
loader
// webpack.config.js
module: {
rules: [
{
test: /\.(graphql|gql)$/,
exclude: /node_modules/,
loader: 'graphql-tag/loader',
},
],
},Webpack loader
# UserDisplay.graphql
query GetUser($id: ID!) {
user(id: $id) {
id
firstName
lastName
}
}Webpack loader
// UserDisplay.tsx
import React from 'react';
import { useQuery } from '@apollo/react-hooks';
import { GetUser } from './UserDisplay.graphql';
function UserDisplay(){
const { loading, error, data } = useQuery(GetUser, {
variables: { id: '1' }
});
...
}Webpack loader
Types

Types
// operations.d.ts
declare module '*/UserDisplay.graphql' {
import { DocumentNode } from 'graphql';
const defaultDocument: DocumentNode;
export const GetUser: DocumentNode;
export default defaultDocument;
}Types

Types
Types

Types

Types
$ apollo client:codegen --target=typescript

Types
// UserDisplay.tsx
import React from 'react';
import { useQuery } from '@apollo/react-hooks';
import Query from './UserDisplay.graphql';
import { GetUser, GetUserVariables } from './__generated__';
function UserDisplay(){
const { loading, error, data } = useQuery<GetUser, GetUserVariables>(Query.GetUser, {
variables: { id: '1' }
});
...
}Types
Abstracting logic
// UserDisplay.boilerplate.tsx
import React from 'react';
import Query from './UserDisplay.graphql';
import { GetUser, GetUserVariables } from './__generated__';
import { useQuery, QueryHookOptions } from '@apollo/react-hooks';
export function useGetUserQuery(options?: QueryHookOptions<GetUser, GetUserVariables>){
return useQuery<GetUser, GetUserVariables>(Query.GetUser, options);
}
export type GetUserQueryHookResult = ReturnType<typeof useGetUserQuery>;Abstracting logic
// UserDisplay.tsx
import React from 'react';
import { useGetUserQuery } from './UserDisplay.boilerplate';
function UserDisplay(){
const { loading, error, data } = useGetUserQuery({variables: { id: '1' } });
...
}Abstracting logic
-
Fully typed
Pros
-
Compiled document node
-
Readability and ease of use
-
So much boilerplate!
-
Boilerplate!
-
Sooo much boilerplate!!!
Cons
Old workflow
-
Write GraphQL document
-
Generate types
-
Create boilerplate file
-
Create wrapped hooks
-
Create alias types
-
Use wrapped hook and alias types
What if this...
-
Write GraphQL document
-
Generate types -
Create boilerplate file -
Create wrapped hooks -
Create alias types -
Use wrapped hook and alias types
... becomes this?
-
Write GraphQL document
-
Generate all the things! -
Use wrapped hook and alias types

-
Parse GraphQL documents
-
Validate against GraphQL schema
-
Generate based on plugin config
GraphQL code generator
Config file
overwrite: true
schema: schema.graphql # Path to schema file OR GraphQL endpoint
generates:
src/__generated__/types.ts:
plugins:
- typescript
src/__generated__/operations.d.ts:
documents: src/pages/**/*.graphql
plugins:
- typescript-graphql-files-modules
src/pages/:
documents: src/pages/**/*.graphql
preset: near-operation-file
presetConfig:
baseTypesPath: __generated__/types.ts
extension: .generated.tsx
plugins:
- typescript-operations
- typescript-react-apollo
config:
documentMode: external
importDocumentNodeExternallyFrom: near-operation-file
withHooks: true
withComponent: false
withHOC: false
reactApolloVersion: 3How it works
# UserDisplay.graphql
query GetUser($id: ID!) {
user(id: $id) {
id
firstName
lastName
}
}How it works
$ graphql-codegen
// UserDisplay.tsx
import React from 'react';
import { useGetUserQuery } from './UserDisplay.generated';
function UserDisplay(){
const { loading, error, data } = useGetUserQuery({variables: { id: '1' } });
...
}How it works
How it works
overwrite: true
schema: schema.graphql # Path to schema file OR GraphQL endpoint
generates:
src/__generated__/types.ts:
plugins:
- typescript
src/__generated__/operations.d.ts:
documents: src/pages/**/*.graphql
plugins:
- typescript-graphql-files-modules
src/pages/:
documents: src/pages/**/*.graphql
preset: near-operation-file
presetConfig:
baseTypesPath: __generated__/types.ts
extension: .generated.tsx
plugins:
- typescript-operations
- typescript-react-apollo
config:
documentMode: external
importDocumentNodeExternallyFrom: near-operation-file
withHooks: true
withComponent: false
withHOC: false
reactApolloVersion: 3// types.ts
export type Maybe<T> = T | null;
/** All built-in and custom scalars, mapped to their actual values */
export type Scalars = {
ID: string,
String: string,
Boolean: boolean,
Int: number,
Float: number,
};
export type Query = {
__typename?: 'Query',
user: User,
};
export type QueryUserArgs = {
id: Scalars['ID']
};
export type User = {
__typename?: 'User',
id: Scalars['ID'],
firstName: Scalars['String'],
lastName: Scalars['String'],
};
How it works
How it works
overwrite: true
schema: schema.graphql # Path to schema file OR GraphQL endpoint
generates:
src/__generated__/types.ts:
plugins:
- typescript
src/__generated__/operations.d.ts:
documents: src/pages/**/*.graphql
plugins:
- typescript-graphql-files-modules
src/pages/:
documents: src/pages/**/*.graphql
preset: near-operation-file
presetConfig:
baseTypesPath: __generated__/types.ts
extension: .generated.tsx
plugins:
- typescript-operations
- typescript-react-apollo
config:
documentMode: external
importDocumentNodeExternallyFrom: near-operation-file
withHooks: true
withComponent: false
withHOC: false
reactApolloVersion: 3// operations.d.ts
declare module '*/UserDisplay.graphql' {
import { DocumentNode } from 'graphql';
const defaultDocument: DocumentNode;
export const GetUser: DocumentNode;
export default defaultDocument;
}How it works
How it works
overwrite: true
schema: schema.graphql # Path to schema file OR GraphQL endpoint
generates:
src/__generated__/types.ts:
plugins:
- typescript
src/__generated__/operations.d.ts:
documents: src/pages/**/*.graphql
plugins:
- typescript-graphql-files-modules
src/pages/:
documents: src/pages/**/*.graphql
preset: near-operation-file
presetConfig:
baseTypesPath: __generated__/types.ts
extension: .generated.tsx
plugins:
- typescript-operations
- typescript-react-apollo
config:
documentMode: external # 'graphQLTag', 'external' or 'documentNode'
importDocumentNodeExternallyFrom: near-operation-file # only works with 'external'
withHooks: true
withComponent: false
withHOC: false
reactApolloVersion: 3// UserDisplay.generated.tsx
import * as Types from './types'; // This path is relative path to the types.ts file
import * as Operations from './UserDisplay.graphql';
import * as ApolloReactCommon from '@apollo/react-common';
import * as ApolloReactHooks from '@apollo/react-hooks';
export type GetUserQueryVariables = {
id: Types.Scalars['ID']
};
export type GetUserQuery = (
{ __typename?: 'Query' }
& { user: (
{ __typename?: 'User' }
& Pick<Types.User, 'id' | 'firstName' | 'lastName'>
) }
);
export function useGetUserQuery(
baseOptions?: ApolloReactHooks.QueryHookOptions<GetUserQuery, GetUserQueryVariables>
){
return ApolloReactHooks.useQuery<GetUserQuery, GetUserQueryVariables>
(Operations.GetUser, baseOptions);
}
export function useGetUserLazyQuery(
baseOptions?: ApolloReactHooks.LazyQueryHookOptions<GetUserQuery, GetUserQueryVariables>
){
return ApolloReactHooks.useLazyQuery<GetUserQuery, GetUserQueryVariables>
(Operations.GetUser, baseOptions);
}
export type GetUserQueryHookResult = ReturnType<typeof useGetUserQuery>;
export type GetUserQueryResult =
ApolloReactCommon.QueryResult<GetUserQuery, GetUserQueryVariables>;Other plugins
We are hiring!
Software Engineer
Thank you!
Website: Github: LinkedIn:
https://eddy.works https://github.com/eddeee888 https://www.linkedin.com/in/eddeee
Improving GraphQL and Apollo Workflow
By Eddy Nguyen
Improving GraphQL and Apollo Workflow
- 934