Eddy Nguyen
React, GraphQL and TypeScript
Simple example
Understanding how it works
Improving workflow
JavaScript UI framework
Awesome
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.
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' }
});
...
}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":[]}
]
}
}]
}
}]
};Describes GraphQL operations and fields
Document Node is the object representation
graphql-tag
+ Easy to use
- gql sent to client
- Doc parsed at runtime
Webpack loader
+ Compiled on server
- Need webpack
graphql-tag
+ Easy to use
- gql sent to client
- Doc parsed at runtime
Pure document node
+ No external libraries
- Impossible to write
Webpack loader
+ Compiled on server
- Need webpack
graphql-tag
+ Easy to use
- gql sent to client
- Doc parsed at runtime
Webpack loader
Typescript
Abstract logic
// webpack.config.js
module: {
rules: [
{
test: /\.(graphql|gql)$/,
exclude: /node_modules/,
loader: 'graphql-tag/loader',
},
],
},# UserDisplay.graphql
query GetUser($id: ID!) {
user(id: $id) {
id
firstName
lastName
}
}// 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' }
});
...
}// operations.d.ts
declare module '*/UserDisplay.graphql' {
import { DocumentNode } from 'graphql';
const defaultDocument: DocumentNode;
export const GetUser: DocumentNode;
export default defaultDocument;
}$ apollo client:codegen --target=typescript
// 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' }
});
...
}// 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>;// UserDisplay.tsx
import React from 'react';
import { useGetUserQuery } from './UserDisplay.boilerplate';
function UserDisplay(){
const { loading, error, data } = useGetUserQuery({variables: { id: '1' } });
...
}Fully typed
Compiled document node
Readability and ease of use
So much boilerplate!
Boilerplate!
Sooo much boilerplate!!!
Write GraphQL document
Generate types
Create boilerplate file
Create wrapped hooks
Create alias types
Use wrapped hook and alias types
Write GraphQL document
Generate types
Create boilerplate file
Create wrapped hooks
Create alias types
Use wrapped hook and alias types
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
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# UserDisplay.graphql
query GetUser($id: ID!) {
user(id: $id) {
id
firstName
lastName
}
}$ graphql-codegen
// UserDisplay.tsx
import React from 'react';
import { useGetUserQuery } from './UserDisplay.generated';
function UserDisplay(){
const { loading, error, data } = useGetUserQuery({variables: { id: '1' } });
...
}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'],
};
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;
}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>;Software Engineer
Website: Github: LinkedIn:
https://eddy.works https://github.com/eddeee888 https://www.linkedin.com/in/eddeee