実際のコード

https://github.com/superyusuke/react-typescript-apollo-bff-codegen

youtube へのリンク

https://youtu.be/XJSTqUQQ9GQ

GraphQL Codegen を用いて GraphQL のコードから型安全な TypeScript 開発に必要なコードを生成できる

Sever 側

  • Schema に対応した TypeScript の型を生成する
  • Resovler の型を Schema から生成する
  • Server の Context の型を生成する(Apollo Server を立てるときに外から注入した DB や、Apollo Sever への request に含まれる header 情報等のこと。context は resolver から参照できる)
  • 独自に定義した Scalar = 型情報 に対応する TypeScript の型を生成する

Client 側

  • Schema に対応した TypeScript の型を生成する
  • typescript-operations: .tsx に書いた query, mutation 等から対応する型を生成する
  • typescript-react-apollo: .tsx に書いた query, mutation 等から対応する hooks を生成する

Server 側の環境設定

パッケージのインストール

# codegen に必要なパッケージの追加
yarn add @graphql-codegen/cli @graphql-codegen @graphql-codegen/typescript @graphql-codegen/typescript-resolvers @graphql-codegen/introspection

codegen の設定ファイルを書く

# codegen.yml

overwrite: true
generates:
  # GraphQL の Resolver に必要な型をつける
  # Resolver は実際に値を DB から取得したり API を使用する部分
  ./src/types/generated/graphql.ts:
    # Schema の場所を指定
    schema: ./src/graphModules/*/index.ts
    config:
      useIndexSignature: true
      # context:
      # Apollo Server を立てるときに外から注入した DB や、
      # Apollo Sever への request に含まれる header 情報等のこと
      # context は resolver から参照できる
      contextType: ../context#Context
      # scalars: Apollo Server がデフォルトで持っている以外の
      # 基本的な型を定義することができる
      scalars:
        # 主に画像アップロード時に必要になる型
        Upload: ../scalars#Upload
    # codegen は plugin を用いて適切な code を生成する
    plugins:
      - typescript # typescript に出力するために必要
      - typescript-resolvers # graphQL の Resolver のための型にするために必要

  # これはなくても多分いい
  # IDE で補完させるために自分は作っている / サーバーサイドではいらないかも
  ./graphql.schema.json:
    schema: ./src/graphModules/*/index.ts
    config:
      useIndexSignature: true
      contextType: ../context#Context
      scalars:
        Upload: ../scalars#Upload
    plugins:
      - introspection

npm コマンドを実行して書き出す

"codegen-server:watch": "gql-gen --watch" : 監視 "codegen-server": "gql-gen" : 一度きり

何が書き出されるか

Schema に対応した TypeScript の型を生成する

例えば以下の GraphQL の schema 定義は、そのままでは TypeScript からは参照できない。これに対応する TtypeScript の型定義を生成する必要がある。

type Item {
    id: Int!
    name: String!
  }

上記 schema から以下の TS 型定義を生成することができる。

export type Item = {
  __typename?: 'Item';
  id: Scalars['Int'];
  name: Scalars['String'];
};

Resovler の型を Schema から生成する

Resolver は実際に値を DB から取得したり API を使用する定義を書く部分。TypeScript でかく。この GraphQL の Resolver に必要な型を生成する。

// schema
export const typeDefs = gql`
  type Query {
    _dummy: Boolean
    healthCheckMessage: String!
  }
  type Mutation {
    _dummy: Boolean
  }
  type Subscription {
    _dummy: Boolean
  }
`;

// schema から生成された Resovlers という型を Resolver につけることで型がわかる
export const resolvers: Resolvers = {
  Query: {
    healthCheckMessage: async () => {
      await delay(1000);
      return "this is health check message. OK";
    },
  },
};

Resolver の型をつけたことで、おかしければエラーになる。

export const resolvers: Resolvers = {
  Query: {
    // 誤った operation 名を定義すると TS 型エラーになる
    AAAAAAhealthCheckMessage: async () => {
      await delay(1000);
      // 文字列を返すべき場所で number を返すと、型エラーになる
      return 999999;
    },
  },
};

Server の Context の型を生成する

Server の Context の型を生成する(Apollo Server を立てるときに外から注入した DB や、Apollo Sever への request に含まれる header 情報等のこと。context は resolver から参照できる)

まず context の型を定義する。

context.d.ts
type Token = string;

type Item = {
  name: string,
  id: number}

type DB = {
  items: Item[]
  currentId: number
}

export type Context = {
  token: Token;
  db: DB
};

設定ファイルで context.d.ts を参照する。この参照は「生成された graphql.ts から見て相対パスでどこにあるか」を指定する。少しこんがらがる。

# codegen.yml

overwrite: true
generates:
  ./src/types/generated/graphql.ts:
    schema: ./src/graphModules/*/index.ts
    config:
      useIndexSignature: true
      contextType: ../context#Context ## ここで context.d.ts を参照させる
      scalars:
        Upload: ../scalars#Upload
    plugins:
      - typescript
      - typescript-resolvers

すると resolver の context に型がつき、token や db がくることがわかる。これをしないと、any になってしまう。

Screen Shot 2020-06-27 at 13.54.38

独自に定義した Scalar = 型情報 に対応する TypeScript の型を生成する

import { FileUpload } from 'graphql-upload';

export type Upload = Promise<FileUpload>;
# codegen.yml

overwrite: true
generates:
  ./src/types/generated/graphql.ts:
    schema: ./src/graphModules/*/index.ts
    config:
      useIndexSignature: true
      contextType: ../context#Context
      scalars:
        Upload: ../scalars#Upload # 独自定義した scalar を読み込ませる
    plugins:
      - typescript
      - typescript-resolvers

Client 側

Operation = Query, Mutation 等を gql タグで書いたら、そのための hooks が生成される。

schema: http://localhost:8080/graphql
documents:
  - ./src/**/*.tsx
  - ./src/**/*.ts
overwrite: true
generates:
  ./src/types/generated/graphql.ts:
    plugins:
      - typescript
      - typescript-operations # .tsx に書いた query, mutation 等から対応する型を生成する
      - typescript-react-apollo # .tsx に書いた query, mutation 等から対応する hooks を生成する
    config:
      skipTypename: false
      withHooks: true
      withHOC: false
      withComponent: false
  ./graphql.schema.json:
    plugins:
      - introspection

以下のように書くだけで、それを使うための react-hooks とそれによって取得できる型が生成される。

gql`
  query blar {...}
`

Screen Shot 2020-06-27 at 14.01.19

まとめ

React, TypeScript, GraphQL による型安全なアプリケーション開発は、実はほとんと GraphQL Codegen のおかげである。開発に必要になる GoraphQL Codegen の設定を全て紹介した。TypeScript による高速堅牢な開発を推進してほしい。