フロントは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
- GraphQLであればGraphql Code Generator
などがあります。
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をインストールします。
$ npm install --save-dev @openapitools/openapi-generator-cli
3. コマンドを実行する
npm scriptに下記のようなコマンドを用意し、npm run codegen
を実行します。
"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で書かれた下記の様なコードだったとします。
export class CreateUserBodyDto {
email: string;
}
export class CreateUserResponseDto {
name: string;
}
@Controller('users')
export class UsersController {
@Post()
createUser(@Body() createUserDto: CreateUserBodyDto): CreateUserResponseDto {
return { name: 'Test' };
}
}
このコードから作られたswagger.jsonを元にcodegenコマンドを実行すると、自動生成されたコードでAPIを下記のように呼び出すことができます。
import { Configuration, DefaultApi, CreateUserBodyDto } from '../codegen/api-client';
export class ApiClientRepository extends DefaultApi {
constructor() {
super(new Configuration({ basePath: 'http://<your domain>/' }));
}
}
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をインストールします。
$ npm install --save-dev @graphql-codegen/cli
TypeScript, React(+Apollo client)で利用する場合は下記のプラグインをインストールし、合わせてcodegen.yml
も更新します。
$ npm install --save-dev @graphql-codegen/typescript @graphql-codegen/typescript-resolvers @graphql-codegen/typescript-react-apollo
overwrite: true
schema: 'http://<your domain>/graphql'
documents: 'src/**/*.graphql'
generates:
./src/graphql/types.ts:
plugins:
- 'typescript'
- 'typescript-resolvers'
- 'typescript-react-apollo'
3. コマンドを実行する
サーバー側が、NestJSで書かれた下記の様なコードだったとします。
@ArgsType()
export class SampleArgsDto {
@Field(() => String, { nullable: true })
sampleId?: string;
}
@ObjectType()
export class SampleData {
@Field()
sampleId: string;
}
@Resolver()
export class SampleResolver {
@Query(() => SampleData)
sample(@Args() { sampleId }: SampleArgsDto): SampleData {
return { sampleId };
}
}
このコードに合わせ、フロントで下記の様なqueryを用意します。
query getSample($sampleId: String) {
sample(sampleId: $sampleId) {
sampleId
}
}
そしてnpm scriptに下記のようなコマンドを用意し、npm run codegen
を実行します。
"scripts": {
"codegen": "graphql-codegen --config codegen.yml"
}
すると、 ./graphql
フォルダにコードが自動生成されます。
4. 自動生成されたコードを呼び出してみる。
前ステップで自動生成されたコードを使うと、下記のようにqueryを呼び出すことができます。
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で型定義をフロントとサーバーとで共有することができました。
さらにプラスでエンドポイントを呼び出すためのコードまで自動生成され、実装がとても楽になっている様子が感じられたと思います。
どちらも事前準備が少し面倒ですが、一度導入すると以降の開発がかなり楽になるのでおすすめです。
ただし一度キメると辞められなくなりますので、導入の際にはご注意を。