GraphQL Advent Calendarの16日目です。
この記事ではGraphQLのスキーマから型ファイルを生成するツールであるgraphql-code-generatorを紹介します。
似たようなツールであるgraphqlgenとの違いも簡単に説明します。
TL;DR
- GraphQLスキーマとは別にスキーマに対応したモデルやリゾルバのシグネチャなどのファイルを用意するのは冗長な作業でなんとかしたい
- graphql-code-generatorを使えばGraphQLスキーマに対応したTypescriptなどの言語の定義ファイルを生成してくれる
- プラグインが豊富でサーバーサイドだけでなくreact-apolloなどに対応したコードの生成も可能
- prismaのgraphqlgenもあるけど機能的にはgraphql-code-generatorのほうが充実している
- リゾルバーだけならgraphqgenで十分
背景
GraphQLにはGraphQL SDL (Schema Definition Language)とよばれるAPIを定義するための独自の言語があります。
もちろんSDLを書くだけでは不十分で「このフィールドに対してはこの処理をしてこの値を返す」というような
リゾルバーを実装する必要があります。実装に際してスキーマの型に対応したTypescriptの型を定義したり、
リゾルバーのシグネチャを定義するのですが、スキーマとTypescriptでの型定義とで大分重複するところがあり (というかほぼ同じ)、なんか冗長な作業だなと思っていました。
なんかいいツール無いかなと調べて出会ったのがgraphql-code-generatorです。
今すぐ試してみたい方はライブデモをどうぞ
graphql-code-generatorとは
GraphQLスキーマからTypescriptの定義ファイルを生成してくれるツールです。1
- スキーマファースト
スキーマを元にコードを生成するので、実装言語での冗長な作業が防げる。 - 型安全
リゾルバーの引数なども全て型を与えるので、打ち間違えなどで存在しないフィールドにアクセスしてしまうみたいな問題を防げる
graphql-code-generatorの仕組み
graphql-code-generatorはデフォルトでcodegen.yml
とcodegen.json
2という設定ファイルを読みに行き
schema: ./schema.graphql
generates:
./server.ts:
plugins:
- add: "/* コメントなど任意のコードを追加できます。 */"
- time
- typescript-common
- typescript-server
./resolver.ts:
plugins:
- typescript-common
- typescript-resolvers
上の./codegen.yml
で使われているプラグインの説明を簡単にしておきます。
- add
任意の文字列を出力する。
tslintをコメントで設定したりだとか、サーバー側とクライアント側で共有したいコードをimport
したりしたいときに使えそうです。 - time
出力時の時間を出力する。 - typescript-common
列挙体、スカラー、入力型のようなサーバー側とクライアント側で共通して使うようなものを出力する。 - typescript-resolvers
サーバーサイドのリゾルバーのシグネチャの出力する。 - typescript-server
サーバーが使うコード (types, directives, interfaces, unions) を出力する。
他にもqueryやmutationなどのドキュメンのからクライアント側が使うコードを出力するプラグインや、
react-apolloのコンポーネントを出力するものなどの色々あります。
詳しくは公式ページをご覧になってください。
実際に使ってみる
まずはpeerDependencyであるgraphql
とgraphql-code-generator
を入れましょう。
次に必要なプラグインもインストールしちゃいます。ここでは上で紹介したプラグインのみを入れます。
(以下ではyarn
を使っていますが、npm
を使ってもいいと思います。)
yarn add -D graphql graphql-code-generator
yarn add -D graphql-codegen-add graphql-codegen-time graphql-codegen-typescript-common graphql-codegen-typescript-resolvers graphql-codegen-typescript-server
次にcodegen.yml
をプロジェクトのルートに記述します。今回は上述のものを使います。
次にスキーマファイルを用意します。
schema {
query: Query
}
type Query {
user(id: ID!): User
}
enum Role {
USER
ADMIN
}
type User {
id: ID!
username: String!
email: String!
role: Role!
}
あとはgraphql-code-generatorのスクリプトであるgql-gen
を実行するだけなのですが、
グローバルにインストールしていないのでpackage.json
にスクリプトを追加します。
...
"scripts": {
...
"gen": "gql-gen",
...
},
...
これでコードを生成する準備ができましたので生成してみましょう!
yarn gen
プロジェクトルートにserver.ts
とresolver.ts
ができていると思います。
中身の詳細は説明しませんが、主に以下の内容が出力されています。
- Roleに対応する列挙体
- リゾルバーのシグネチャ
- オブジェクトの型(
Query
とUser
) -
user(id: ID!): User
の引数の型
あとはこのファイルを別ファイルでインポートして使うだけです。
graphqlgenとの違い
似たようなツールとしてPrismaのgraphqlgenがあります。
graphqlgenのGitHubのIssueにGraphQL Code Generatorの著者の方が違いはどこにあるのかという質問をしています。
graphqlgenの著者の回答を簡単にまとめると、
graphqlgenはJavascriptベースの型言語で、サーバーサイドのGraphQLリゾルバーのコード生成に特化しているのに対して、graphql-code-generatorはより一般的なツールであるということです。
具体的には、
- フロントエンドとバックエンドのスコープ
- graphqlgenはサーバーサイドのリゾルバー生成に特化しているので、モデルなどは自分で定義する必要がある
- graphql-code-generatorはreact-apolloなどフロントエンドも網羅している
- サポートするプログラミング言語
graphqlgenはTypescript, FlowなどのJavascriptのエコシステムに特化している。
これに対して、graphql-code-generatorの作者はgraphql-code-generatorも元々Typescriptファーストで作られており、サポートをする言語の汎化を行っているとは一切言っていないと反論しています。
確かにドキュメントを見た感じでは生成されたファイルはすべてがTypescriptとなっているのでこの主張はよくわかりません。
ただ、graphql-code-generatorはプラグインを自前で用意することで基本的にTypescript以外の言語に出力することは可能です。 - APIの設計や設定
graphql-code-generatorの設計はgraphqlgenの開発者が満足できるものではなかったようです。
使ってみた感想
- graphqlgenはリゾルバに特化しているのでシンプルな一方で、スキーマ以外にも用意しないといけないファイルがあるのでちょっと面倒です。それに対して、graphql-code-generatorはスキーマを食わせるだけで、リゾルバーのインターフェスはもちろんモデルも生成してくるのでかなり便利だと思います。
- graphql-code-generatorはファイルごとに変換処理が独立しているので、共通するコードを切り分けてimportしてくれません。
そうすると、typescript-common
の出力結果である列挙型の定義がどちらのファイルにも現れてしまいます。
ファイルごとに使うプラグインを定義するので難しいかもしれませんが、もう少し上手い方法があってもいいと思います。
それに対してgraphqlgenはファイルの分割戦略を設定で変更することができ、importもしてファイルを関連付けてくれます。
まとめ
今回はGraphQLのスキーマからTypescriptなどのコードを生成するツールであるgraphql-code-generatorとgraphqlgenを紹介しました。
graphql-code-generatorのほうが機能は充実しているのですが、graphqlgenのいいところ(ファイルの分割戦略)もあります。
GitHubのissueを見ると対立的な雰囲気があるので、お互いが協力しながら一つのよいOSSを作り上げていってほしいと思っています。
-
プラグインを作成可能なので他の言語も対応可能です。
Golangでは似たツールとしてgqlgenがあります。
このツールには以下の特徴があります。 ↩ -
設定で変更可能です。
どのパイプラインで各ファイルを生成するかを決めます。
例えば以下のcodegen.yml
では、スキーマ./schema.graphql
から./server.ts
と./resolver.ts
を生成する設定をしています。
各ファイルの生成パイプラインはplugins
以下に列挙して指定します。
公式にも書かれているとおり、プラグインごとにスキーマが独立して処理され、各プラグインの出力が単純に上から順番にconcatenateされるだけです。
これは何を意味するかというと、各ファイルでプラグインを共有していたとしても(以下の例だとtypescript-common
)、重複するコードが各ファイルに出力されてしまうということです。
後ほど説明するadd
というプラグインを使って共通なコードをimport
するコードをインジェクトするのが解決策の一つかなと思います。 ↩