コード譜投稿アプリを作る#5 (graphql-code-generatorで型定義する)

[←前回の記事](https://qiita.com/t_sekky/items/f589877d575fa1f480e4)


# 今回やること
前回はフロント側からgraphqlクエリを叩いて`Score`の一覧を表示しましたが、`Score`の型はこちらで手動で作りました。
前回のコード↓
``` tsx:index.tsx
...

type Score = {
  id: Number
  title: String
}

...
```

これを[graphql-code-generator](https://github.com/dotansimha/graphql-code-generator)でコマンド一発で型定義を作成できるようにします。

# ライブラリのインストール
まずライブラリをインストールします。
```
$ yarn add -D @graphql-codegen/cli
```
他にも必要なプラグインをインストール
```
$ yarn add --dev @graphql-codegen/typescript @graphql-codegen/typescript-graphql-request @graphql-codegen/typescript-operations @graphql-codegen/typescript-react-apollo
```

# セットアップ
以下のコマンドを実行すると設定ファイルを対話式で作ってくれます。（設定ファイルを自分で作ってもよいです）
```
$ npx graphql-codegen init
```

色々聞かれるので答えていきます。
私が回答した内容は以下です
```
? Where is your schema?: (path or url) http://localhost:3000/graphql
? Where are your operations and fragments?: ./graphql/documents/**/*.graphql
? Pick plugins: TypeScript (required by other typescript plugins), TypeScript Operations (operations and fragments), TypeScript React Apollo (typed 
components and HOCs)
? Where to write the output: ./graphql/generated.tsx
? Do you want to generate an introspection file? No
? How to name the config file? codegen.yml
? What script in package.json should run the codegen? codegen
```

一応大事そうなところを説明すると、
> Where is your schema?

サーバーサイドのgraphqlのエンドポイントを書いてます。このエンドポイントからschemaを読み取って型定義などしてるっぽいです。
> Where are your operations and fragments?

フロントで使うqueryやmutationなどを書くファイル（これから作ります）のパスを作ります。今回は`graphql`というフォルダを作って、queryは`/graphql/query/〇〇.graphql`、mutationは`/graphql/mutation/〇〇.graphql`に書こうと思っているので、このようにしています。
> Where to write the output?

コマンドを打った時にここに型定義が出力されます。
> How to name the config file? 
What script in package.json should run the codegen? 

`package.json`に設定に応じたコマンドが追加されます。

できた設定ファイルがこちら
```yml:codegen.yml
overwrite: true
schema: "http://localhost:3000/graphql"
documents: "./graphql/documents/**/*.graphql"
generates:
  ./graphql/generated.tsx:
    plugins:
      - "typescript"
      - "typescript-operations"
      - "typescript-react-apollo"
```

`package.json`にもコマンドが追加されています。
```json:package.json
  ...
  "private": true,
  "scripts": {
    "dev": "next dev -p 3001",
    "build": "next build",
    "start": "next start",
+   "lint": "next lint",
+   "codegen": "graphql-codegen --config codegen.yml"
  },
  "dependencies": {
  ...
```

# コマンド実行
以上で設定は完了しました！
コマンドを実行する前にフロントで使用するqueryを書いていきます。
前回使ったScoreの一覧を取得するqueryを書いていきます。

```graphql:/graphql/documents/query/scores.graphql
query Scores {
  scores {
    id
    title
  }
}
```

これでコマンドを実行します。
yarn以下は`package.json`にさきほど追記されたコマンドですね

```
$ yarn codegen
```

無事、以下のように型定義が作成されました！
```tsx:/graphql/generated.tsx
import { gql } from '@apollo/client';
import * as Apollo from '@apollo/client';
export type Maybe<T> = T | null;
export type InputMaybe<T> = Maybe<T>;
export type Exact<T extends { [key: string]: unknown }> = { [K in keyof T]: T[K] };
export type MakeOptional<T, K extends keyof T> = Omit<T, K> & { [SubKey in K]?: Maybe<T[SubKey]> };
export type MakeMaybe<T, K extends keyof T> = Omit<T, K> & { [SubKey in K]: Maybe<T[SubKey]> };
const defaultOptions = {} as const;
/** All built-in and custom scalars, mapped to their actual values */
export type Scalars = {
  ID: string;
  String: string;
  Boolean: boolean;
  Int: number;
  Float: number;
  ISO8601DateTime: any;
};

export type Mutation = {
  __typename?: 'Mutation';
  /** An example field added by the generator */
  testField: Scalars['String'];
};

export type Query = {
  __typename?: 'Query';
  score: Score;
  scores: Array<Score>;
};


export type QueryScoreArgs = {
  id?: InputMaybe<Scalars['Int']>;
};

export type Score = {
  __typename?: 'Score';
  createdAt: Scalars['ISO8601DateTime'];
  id: Scalars['ID'];
  title?: Maybe<Scalars['String']>;
  updatedAt: Scalars['ISO8601DateTime'];
};

export type ScoresQueryVariables = Exact<{ [key: string]: never; }>;


export type ScoresQuery = { __typename?: 'Query', scores: Array<{ __typename?: 'Score', id: string, title?: string | null }> };


export const ScoresDocument = gql`
    query Scores {
  scores {
    id
    title
  }
}
    `;

/**
 * __useScoresQuery__
 *
 * To run a query within a React component, call `useScoresQuery` and pass it any options that fit your needs.
 * When your component renders, `useScoresQuery` returns an object from Apollo Client that contains loading, error, and data properties
 * you can use to render your UI.
 *
 * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
 *
 * @example
 * const { data, loading, error } = useScoresQuery({
 *   variables: {
 *   },
 * });
 */
export function useScoresQuery(baseOptions?: Apollo.QueryHookOptions<ScoresQuery, ScoresQueryVariables>) {
        const options = {...defaultOptions, ...baseOptions}
        return Apollo.useQuery<ScoresQuery, ScoresQueryVariables>(ScoresDocument, options);
      }
export function useScoresLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<ScoresQuery, ScoresQueryVariables>) {
          const options = {...defaultOptions, ...baseOptions}
          return Apollo.useLazyQuery<ScoresQuery, ScoresQueryVariables>(ScoresDocument, options);
        }
export type ScoresQueryHookResult = ReturnType<typeof useScoresQuery>;
export type ScoresLazyQueryHookResult = ReturnType<typeof useScoresLazyQuery>;
export type ScoresQueryResult = Apollo.QueryResult<ScoresQuery, ScoresQueryVariables>;
```

# 作成された型定義を使用して書き換え
実際に生成された型定義を使って、前回作った`index.tsx`を書き換えてみたのが以下です。

```tsx:/pages/index.tsx
import { Box } from '@chakra-ui/react'
import type { ReactElement } from 'react'
import { Layout } from '../components/common/Layout'
import type { NextPageWithLayout } from './_app'
import { useQuery } from "@apollo/client";
import { Score, ScoresDocument } from '../graphql/generated';

const Page: NextPageWithLayout = () => {
  const { data, loading, error } = useQuery(ScoresDocument)
  if (loading) return <Box>ロード中...</Box>;
  if (error) return <Box>{error.message}</Box>;
  return (
    <Box>
      <ul>
        {data.scores.map((score: Score) => (
          <li>{score.title}</li>
        ))}
      </ul>
    </Box>
  )
}

Page.getLayout = function getLayout(page: ReactElement) {
  return (
    <Layout>
      {page}
    </Layout>
  )
}

export default Page
```

queryも`ScoresDocment`という形で書き換えられ、前回自力で作成した`Score`の型定義もサーバーサイドのschemaを使用して生成されたものなので、型づけの安全性が高まっています！

今回はここまで！

# 参考にさせていただいたサイト・記事
[graphql-code-generator](https://github.com/dotansimha/graphql-code-generator)
[初めての GraphQL。Code Generator で型を生成するまでのメモ](https://chaika.hatenablog.com/entry/2022/06/02/083000)