Code GeneratorでTypeScriptの型定義をフロントとサーバーとで共有する方法

フロントはReactやVueにてTypescriptを使い、サーバーはNode.jsでTypeScriptを使う。
そういった構成を採用する際によく発生するのが、API呼び出し周りのロジックにて、フロントとサーバーとでどちらも同じ様な型を重複して定義してしまうことです。

型をフロントとサーバーでコピペして使ってしまうと、最初の方は良いけれどバグ修正や追加対応の際にコピペを忘れ、知らぬうちに差分が出てしまう。
型定義のための共通リポジトリを作るにしても、都度共通リポジトリを更新して、フロント・サーバーの両方でバージョンを上げる作業が面倒。

そんな時の面倒やつらみを一気に解決する便利なツールが、**Code Generator**です。
一度使うと、なぜTypeScriptを使い始めた最初からこれをやらなかったのだろうという気持ちしか湧いてこないくらい開発が楽になるのでおすすめです。

# Code Generatorとは
Code Generatorとは、

- REST APIであればswagger.json
- GraphQLであればshema.json

などをインプットに、サーバー側のエンドポイントを呼び出すファンクションや、それのインプット/アウトプットの型などを自動生成してくれる様なツールを指します。
色々なツールがありますが、例えば

- REST APIであれば[OpenAPI Generator](https://github.com/OpenAPITools/openapi-generator)
- GraphQLであれば[Graphql Code Generator](https://www.graphql-code-generator.com)

などがあります。

# OpenAPI Generatorの使い方

## 1. swagger.jsonを用意する

まず、サーバー側にswagger.jsonを定義します。
サーバー側はNestJSだと、リクエストやレスポンスの型をClassで定義するだけで、そこからswagger.jsonを自動生成するようにできるのでおすすめです。
https://docs.nestjs.com/openapi/cli-plugin#using-the-cli-plugin

## 2. OpenAPI Generator CLIをインストールする

APIの呼び元（今回の場合はフロント）で下記コマンドにてOpen API Generator CLIをインストールします。


```bash
$ npm install --save-dev @openapitools/openapi-generator-cli
```

## 3. コマンドを実行する

npm scriptに下記のようなコマンドを用意し、`npm run codegen`を実行します。

```json:package.json
"scripts": {
  "codegen": "openapi-generator-cli generate -i http://<your domain>/swagger.json -g typescript-axios -o ./codegen/api-client"
}
```

すると、 `./codegen/api-client`フォルダにコードが自動生成されます。
各オプションの意味は下記の通り。

| オプション | 概要 |
|:-:|:-:|
| -i | 対象となるswagger.jsonのurl |
| -g | 自動生成されるファイルのタイプ |
| -o | 自動生成されたファイルの出力先 |

ちなみにNestJSの場合は、swagger.jsonのoperationIdによるprefixを削除する`--remove-operation-id-prefix`をオプションに指定するとメソッド名が綺麗になります。

## 4.　自動生成されたコードを呼び出してみる。

サーバー側が、NestJSで書かれた下記の様なコードだったとします。

```typescript
export class CreateUserBodyDto {
  email: string;
}

export class CreateUserResponseDto {
  name: string;
}
```

```typescript
@Controller('users')
export class UsersController {
  @Post()
  createUser(@Body() createUserDto: CreateUserBodyDto): CreateUserResponseDto {
    return { name: 'Test' };
  }
}
```

このコードから作られたswagger.jsonを元にcodegenコマンドを実行すると、自動生成されたコードでAPIを下記のように呼び出すことができます。

```typescript
import { Configuration, DefaultApi, CreateUserBodyDto } from '../codegen/api-client';

export class ApiClientRepository extends DefaultApi {
  constructor() {
    super(new Configuration({ basePath: 'http://<your domain>/' }));
  }
}
```

```typescript
import { CreateUserBodyDto } from '../codegen/api-client';

const apiClientRepository = new ApiClientRepository();
const input: CreateUserBodyDto = { email: 'test@qiita.com' };
const { name } = apiClientRepository.createUser(input);

console.log(name);
```

`createUser`メソッドの引数と戻り値にはそれぞれ型が定義されてimportもできるため、フロントにてサーバー側の型の定義を再定義することなく利用できていることになります。

# Graphql Code Generatorの使い方

## 1. schema.jsonを用意する

まず、サーバー側にgraphqlのschema.jsonを定義します。
サーバー側はNestJSだと、graohqlを導入する際にcode firstの手法を採用すれば、リクエストやレスポンスの型をClassで定義するだけで、そこからschema.jsonを自動生成するようにできるのでおすすめです。
https://docs.nestjs.com/graphql/quick-start#overview

## 2. Graphql Code Generatorをインストールする

APIの呼び元（今回の場合はフロント）で下記コマンドにてGraphql Code Generatorをインストールします。

```bash
$ npm install --save-dev @graphql-codegen/cli
```

TypeScript, React（+Apollo client）で利用する場合は下記のプラグインをインストールし、合わせて`codegen.yml`も更新します。

```bash
$ npm install --save-dev @graphql-codegen/typescript @graphql-codegen/typescript-resolvers @graphql-codegen/typescript-react-apollo
```

```yaml:codegen.yml
overwrite: true
schema: 'http://<your domain>/graphql'
documents: 'src/**/*.graphql'
generates:
  ./src/graphql/types.ts:
    plugins:
      - 'typescript'
      - 'typescript-resolvers'
      - 'typescript-react-apollo'
```

## 3. コマンドを実行する

サーバー側が、NestJSで書かれた下記の様なコードだったとします。

```typescript
@ArgsType()
export class SampleArgsDto {
  @Field(() => String, { nullable: true })
  sampleId?: string;
}

@ObjectType()
export class SampleData {
  @Field()
  sampleId: string;
}
```

```typescript
@Resolver()
export class SampleResolver {
  @Query(() => SampleData)
  sample(@Args() { sampleId }: SampleArgsDto): SampleData {
    return { sampleId };
  }
}
```

このコードに合わせ、フロントで下記の様なqueryを用意します。

```graphql:get-sample.graphql
query getSample($sampleId: String) {
  sample(sampleId: $sampleId) {
    sampleId
  }
}
```

そしてnpm scriptに下記のようなコマンドを用意し、`npm run codegen`を実行します。

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

すると、 `./graphql`フォルダにコードが自動生成されます。


## 4.　自動生成されたコードを呼び出してみる。

前ステップで自動生成されたコードを使うと、下記のようにqueryを呼び出すことができます。

```tsx:Home.tsx
import React, { useEffect } from 'react';
import { useGetSampleQuery } from '../graphql/types';

const Sample = () => {
  const { data } = useGetSampleQuery({
    variables: { sampleId: 'init' },
  });

  return (
    <div>Response: {data?.sample.sampleId}</div>
  );
};

export default Sample;
```

型だけではなく、作成したqueryを呼び出してデータを取得するhookまでも作成されているので、フロントで必要なの実装はqueryを書くことだけになっています。
とてもシンプルです。

# まとめ
REST API、Graphqlのどちらも全く違う技術ですが、それぞれCode generatorで型定義をフロントとサーバーとで共有することができました。
さらにプラスでエンドポイントを呼び出すためのコードまで自動生成され、実装がとても楽になっている様子が感じられたと思います。
どちらも事前準備が少し面倒ですが、一度導入すると以降の開発がかなり楽になるのでおすすめです。
ただし一度キメると辞められなくなりますので、導入の際にはご注意を。