今更ながら graphql-code-generator の便利さを痛感する

# はじめに

今日も今日とて、フロントReact + バックRailsのSPA + APIのアプリ開発していたところ

TypeScriptのReact側で、react-apolloの型宣言がめんどくさいと思っていました。

バックエンド側はGraphQLを使用しているので、いろんなところに型宣言をしているようにも感じて、微妙。。

そこでgraphql-code-generatorを使っていろいろ気持ち悪い部分を解消していこうという話をします。

# 今回の構成

## フロントエンド

[GitHub](https://github.com/pure-adachi/graphql-code-generator-sample-frontend)

- React（SPAで）
- TypeScript
- create-react-app
- React Apollo

## バックエンド

[GitHub](https://github.com/pure-adachi/graphql-code-generator-sample-backend)

- Ruby
- Rails（APIで）
- GraphQL

※上記２つのリポジトリは[こちら](https://github.com/pure-adachi/graphql-code-generator-sample)のリポジトリから連動させる仕組みとしました。（開発環境として）

## とりあえずApolloの公式通りにやってみる

バックエンド側に`todos`という、Todoモデルにあるデータを全て取得するAPIを作成しておきました。

これをフロントエンド側で取得し、表示します。

```diff:src/App.tsx
+import { gql, useQuery } from "@apollo/client";
 import React from "react";
 import logo from "./logo.svg";
 import "./App.css";

+const TODOS_QUERY = gql`
+  query {
+    todos {
+      name
+   }
+  }
+`;

 const App = () => {
+   const { loading, data } = useQuery(TODOS_QUERY);
+
   return (
     <div className="App">
       <header className="App-header">
         <img src={logo} className="App-logo" alt="logo" />
         <p>
           Edit <code>src/App.tsx</code> and save to reload.
         </p>
-        <a
-          className="App-link"
-          href="https://reactjs.org"
-          target="_blank"
-          rel="noopener noreferrer"
-        >
-          Learn React
-        </a>
+        {loading ? (
+          <p>Loading ...</p>
+        ) : (
+          <ul>
+            {data && data.todos.map(({ name }, i) => <li key={i}>{name}</li>)}
+          </ul>
+        )}
       </header>
     </div>
   );
 };

 export default App;
```

型宣言していないので、エラーが出ましたね。

![スクリーンショット 2020-08-13 21.10.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/304414/1fc6520b-8b50-d11b-2c96-8219f36736a7.png)

型宣言してあげます。

用意する型は、`Todo`モデルの型と、レスポンス値の型です。

レスポンスは`{"data":{"todos": []}`という値が返るようにしています。

```tsx
interface Todo {
  name: string;
}

interface TodosData {
  todos: Todo[];
}
```

`TodosData`を以下のように使います。

```diff
-  const { loading, data } = useQuery(TODOS_QUERY);
+  const { loading, data } = useQuery<TodosData>(TODOS_QUERY);
```

エラーなく実行できました。

![スクリーンショット 2020-08-13 21.15.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/304414/a08f90ad-57e2-84f0-c3ae-d8205a78ec5e.png)


では、`GraphQL Code Generator`が入っていたらどうなるか試してみます。

## GraphQL Code Generatorを使う

### 公式の手順通り、インストールとセットアップ

https://graphql-code-generator.com/docs/getting-started/codegen-config

```sh
$ yarn add -D @graphql-codegen/cli @graphql-codegen/typescript @graphql-codegen/typescript-operations
```

```json:package.json
"scripts": {
  "generate": "graphql-codegen"
}
```

バックエンド側に用意しているエンドポイントは`http://localhost:5000/graphql`なので、`schema`にこれを使います。

```yml:codegen.yml
schema: http://localhost:5000/graphql
documents: ./graphql/queries/*.graphql
generates:
  ./src/types.d.ts:
    plugins:
      - typescript
      - typescript-operations
```

※`documents`オプションを使用する場合に、`@graphql-codegen/typescript-operations`が必要みたいです。

`documents`に指定した場所に、`todos`のクエリを記載します。

```graphql:graphql/queries/todos.graphql
query {
  todos {
    name
  }
}
```

バックエンドを起動してある状態で、用意した`generate`コマンドを実行してみます。

```sh
$ yarn generate
```

`src/types.d.ts`ファイルが生成されました。

```ts:src/types.d.ts
...省略...

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


export type Unnamed_1_Query = (
  { __typename?: 'Query' }
  & { todos: Array<(
    { __typename?: 'Todo' }
    & Pick<Todo, 'name'>
  )> }
);
```

`Unnamed`となってしまっているので、クエリに名前をつけて再度実行します。

```diff:graphql/queries/todos.graphql
-query {
+query todos {
   todos {
     name
   }
 }
```

`src/types.d.ts`ファイルに`TodosQuery`という`Type`が定義されました。

これを`useQuery`の型に利用してみます。


```diff:src/App.tsx
+ import { TodosQuery } from "./types.d";

- const { loading, data } = useQuery<TodosData>(TODOS_QUERY);
+ const { loading, data } = useQuery<TodosQuery>(TODOS_QUERY);
```

同じように動作が確認できました。

![スクリーンショット 2020-08-16 22.05.54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/304414/e43e01c9-8c94-e18f-0d7b-f054c8ffcfad.png)

`src/App.tsx`に`Todo`モデルの型と、レスポンス値の型を定義しなくて良くなりました。

でも、TODOデータを取得する為のクエリを`src/App.tsx`と`graphql/queries/todos.graphql`の２箇所に書いているのが気持ち悪いですよね。

`todos`を取得する為の専用の`useQuery`があれば型もクエリも渡さなく済むのに。。。

### @graphql-codegen/typescript-react-apolloを導入する

> `todos`を取得する為の専用の`useQuery`があれば型もクエリも渡さなく済むのに。。。

ということで、この気持ち悪いを解消していきます。

まずはインストール

```sh
$ yarn add -D @graphql-codegen/typescript-react-apollo
```

`typescript-react-apollo`を追加します。

```diff:codegen.yml
 schema: http://localhost:5000/graphql
 documents: ./graphql/queries/*.graphql
 generates:
   ./src/types.d.ts:
     plugins:
       - typescript
       - typescript-operations
+      - typescript-react-apollo
```

生成コマンドを実行

```sh
$ yarn generate
```

`src/types.d.ts`に`useTodosQuery`という関数が生成されたので使ってみます。

```diff:src/App.tsx
- import { TodosQuery } from "./types.d";
+ import { useTodosQuery } from "./types.d";

- const { loading, data } = useQuery<TodosQuery>(TODOS_QUERY);
+ const { loading, data } = useTodosQuery();
```

ちゃんと動きましたね。

![スクリーンショット 2020-08-16 22.29.40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/304414/eadcc964-55ac-4757-7be4-5684fb4165ea.png)


GraphQLの便利なところとして、同じAPIでも、必要なフィールドのみを取得することができる特徴があります。

先ほど、`todos`のデータを取得する際、`name`のみ指定し取得、一覧表示のような機能を実現しました。

これを個別に、編集、削除といった機能を実現するには、`name`がユニークでない限り、`id`のようなもので、

`todo`を特定する必要があります。

別の画面等で、`name`に加え、`id`も必要な場面があった場合、以下のようなクエリを別で作成したくなってきます。

```graphql
query todos {
  todos {
    id
    name
  }
}
```

しかし、`graphql-code-generator`で生成する関数は全て、`src/types.d.ts`に入るように設定しています。

ここには既に、`name`のみを指定した`todos`を取得するクエリの関数が存在しているので、以下のようなファイルを作成し、

`yarn generate`を実行すると、`Not all operations have an unique name`というエラーが発生します。

```graphql:graphql/queries/todosIncludeId.graphql
query todos {
  todos {
    id
    name
  }
}
```

`query`の右に記載している名前が、ユニークでないといけないってことですね。

```diff:graphql/queries/todosIncludeId.graphql
-query todos {
+query todosIncludeId {
   todos {
     id
     name
   }
 }
```

`query`の名前をユニークな名前に変更してみました。

すると、`src/types.d.ts`に`useTodosQuery`とは別に、`useTodosIncludeIdQuery`関数が生成されました。

GraphQLの便利な特性を潰すことなく利用できますね。 :thumbsup: 

# 参考文献
- [Apollo Docs](https://www.apollographql.com/docs/react/development-testing/static-typing)
- [Graphql Code Generator - getting-started](https://graphql-code-generator.com/docs/getting-started/installation)
- [graphql-codegen で型定義を生成する (React, Apollo, TypeScript) - Qiita](https://qiita.com/mizchi/items/fb9f598cea94d2c8072d)
- [GraphQL Code Generator の使い方。〜GraphQLのSchemaからTSの型定義を自動生成する〜 - Qiita](https://qiita.com/ryo2132/items/601ec3c085fcd99658e5)
- [Graphql Code Generator - typescript-react-apollo](https://graphql-code-generator.com/docs/plugins/typescript-react-apollo)