GraphQLの型定義をスキーマから自動生成する。

GraphQL、型定義をスキーマから自動生成するところまで試してみた。

やってみて気づいたこととして、自動生成する型定義はサーバとクライアントの2種類があるということ。それぞれ必要。

サーバ

サーバの方はschema.graphqlというようなスキーマ定義ファイルがあれば生成できる。

npm install -D @graphql-codegen/cli @graphql-codegen/typescript @graphql-codegen/typescript-resolvers

公式はyamlでConfigを作成していたが、tsでもConfigは作成できる。

import type { CodegenConfig } from '@graphql-codegen/cli';

const config: CodegenConfig = {
  schema: "./src/schema.graphql",
  generates: {
    "./src/__generated__/resolvers-types.ts": {
      plugins: [
        "typescript",
        "typescript-resolvers",
      ],
      config: {
        useIndexSignature: true,
        contextType: "../index#MyContext",
      },
    },
  },
  overwrite: true,
};
export default config;

このファイルをcodegen-server.tsと保存してCLIでgenerateする。

npx graphql-codegen --config codegen-server.ts

これで./src/__generated__/resolvers-types.tsに型定義が出力されるので、importして、Resolverの型を置き換えてやればよい。

最小限のコードだけ示すと以下。

import { Resolvers } from './__generated__/resolvers-types';

const resolvers: Resolvers = {
  Query: {
    ...
  }
};

もちろん、スキーマ中で定義した型もimportできる。

Apollo-serverのドキュメントを参考にした。 Generating types from a GraphQL schema - Apollo GraphQL Docs

クライアント

GraphQLクライアントでは、クエリ毎に取得できるデータは異なる。そのため、クエリ毎に型定義が必要になる。

例えば以下のコードでは、gqlで宣言しているクエリが対象となる。 クエリ名(ここでのGetBooks)は自動生成されるGraphQLの型定義でも利用されるため、クエリ毎にユニークな名前を付ける必要がある。

import { ApolloClient, InMemoryCache } from '@apollo/client/core';
import { gql } from '@apollo/client/core';

const query = gql(`
query GetBooks {
  books {
    id
    title
  }
}
`);

async function query() {
  const client = new ApolloClient({
    uri: 'http://localhost:4000/',
    cache: new InMemoryCache(),
  });

  const { data: { books } } = await client.query({ query });
  books.map(book => {
    console.log({ book });
  })
}

void query();

クライアント用のConfigの定義は以下codegen-client.tsとする。 サーバのときのConfig異なるのは対象スキーマ以外にdocumentsでtsファイルを指定している。(クエリの記述があるコードを指定する必要がある) presetでのclientを指定も必要。

import type { CodegenConfig } from '@graphql-codegen/cli';

const config: CodegenConfig = {
  schema: "./src/schema.graphql",
  documents: ['src/**/*.{ts,tsx}'],
  generates: {
    "./src/__generated__/": {
      preset: 'client',
      plugins: [],
      presetConfig: {
        gqlTagName: 'gql',
      },
    },
  },
  ignoreNoDocuments: true,
  overwrite: true,
};

export default config;

ここでは無難にApolloClientを使っておく。

自動生成対象となるクエリをgqlでDocumentNodeに変換してからclientのqueryに渡すようにしておく。そうするとcodegenを実行した際に必要な型定義が作成の対象となる様子。

型定義の自動生成はサーバと変わらない。以下で実行する。

npx graphql-codegen --config codegen-client.ts

自動生成後は、 @apollo/client/coreからimportしていたgqlを自動生成したgqlに置き換えれば、生成された型が効くようになる。

import { gql } from '@apollo/client/core';
import { gql } from '../src/__generated__/gql';

クライアントのドキュメントはちょっとみつけられなかったので。React用のドキュメントを参考にしつつやってみたという形。 Generating types from a GraphQL schema - Apollo GraphQL Docs