GraphQL に入門していて、GraphQL の schema などから TypeScript の型定義を自動生成するのがナウでヤングだと聞いたので試してみたのメモ。
この記事の対象
- とりあえず GraphQL Code Generator で GraphQL の Schema から型定義ファイルを生成してみたい
- 調べると高度な情報が出てくるけど断片的で、まずどうやって導入するのかにで詰まった
GraphQL Code Generator
GraphQL のスキーマから TypeScript の方を生成できるライブラリ。他にもあるとおもうけど情報が多かったので GraphQL Code Generator を使用することにしました。
インストール
$ npm i -D @graphql-codegen/cli # graphpl も必要なのでインストールする $ npm i graphql
設定ファイルの生成
npm run graphql-codegen init
コマンドを実行すると対話式で設定ファイル (codegen.yml
) が作成できる
Missing script: "graphql-codegen"
になる場合は npx
を使えばOK
$ npm run graphql-codegen init Welcome to GraphQL Code Generator! Answer few questions and we will setup everything for you. # どこで使うか・フレームワークを使うか ? What type of application are you building? (Press <space> to select, <a> to toggle all, <i> to invert selection, and <enter> to proceed) ◯ Backend - API or server ◯ Application built with Angular ❯◯ Application built with React ◯ Application built with Stencil ◯ Application built with other framework or vanilla JS # スキーマの場所 (よく分かってない) ? Where is your schema?: (path or url) (http://localhost:4000) http://localhost:4000 # オペレーションとフラグメントの場所 (よく分かってない) ? Where are your operations and fragments?: (src/**/*.graphql) src/graphql/**/*.graphql # 使用するプラグインの選択 ? Pick plugins: (Press <space> to select, <a> to toggle all, <i> to invert selection, and <enter> to proceed) ❯◉ TypeScript (required by other typescript plugins) ❯◉ TypeScript Operations (operations and fragments) ◯ Flow (required by other flow plugins) ◯ Flow Operations (operations and fragments) ◯ TypeScript React Apollo (typed components and HOCs) ❯◉ TypeScript GraphQL files modules (declarations for .graphql files) ◯ TypeScript GraphQL document nodes (embedded GraphQL document) ◯ Introspection Fragment Matcher (for Apollo Client) ◯ Urql Introspection (for Urql Client) # 型定義ファイルの出力先 ? Where to write the output: (src/generated/graphql.ts) src/generated/graphql.ts # introspection ファイルを生成するか (よく分かってない) ? Do you want to generate an introspection file? (Y/n) Y # 設定ファイル ? How to name the config file? (codegen.yml) codegen.yml # codegen を実行する npm script のメソッド名 ? What script in package.json should run the codegen? codegen Fetching latest versions of selected plugins... Config file generated at codegen.yml $ npm install To install the plugins. $ npm run codegen To run GraphQL Code Generator. # 必要なパッケージをインストール $ npm install
生成された設定ファイル
codegen.yml
overwrite: true # スキーマの場所 schema: "http://localhost:4000" # オペレーションとフラグメントの場所 documents: "src/graphql/**/*.graphql" generates: # 型定義ファイルの出力先 src/generated/graphql.ts: # 使用するプラグインの plugins: - "typescript" - "typescript-operations" - "typescript-graphql-files-modules" ./graphql.schema.json: # introspection ファイルを生成するためのプラグイン plugins: - "introspection"
CLI で理解できていなかった箇所の設定
Configuration options | GraphQL Code Generator
schema
schema (required) - A URL to your GraphQL endpoint, a local path to
.graphql
file, a glob pattern to your GraphQL schema files, or a JavaScript file that exports the schema to generate code from. This can also be an array that specifies multiple schemas to generate code from.schema field
The schema field should point to your GraphQLSchema - there are multiple ways you can specify it and load your GraphQLSchema.
cf. schema field – GraphQL Code Generator
実際に使うGraphQL サーバーのエンドポイントのURLか、GraphQLサーバー側のスキーマを定義したファイルを指定すれば良さそう
documents
documents - Array of paths or glob patterns for files which export GraphQL documents using a
gql
tag or a plain string; for example:./src/**/*.graphql
. You can also provide these options with a string instead of an array if you're dealing with a single document.documents field
The documents field should point to your GraphQL documents: query, mutation, subscription, and fragment.
documents is only required if you use plugins that generate code for the client-side.
cf. documents field – GraphQL Code Generator
クライアントサイドから GraphQL で呼び出す際の Query が書かれているファイルの場所を指定すれば良さそう
introspection
Introspection: 内観
GraphQL Playground は schema の詳細を調べる機能を提供します。Introspection という GraphQL の基本的な技術で、これを用いることによって graph の schema に関する詳細情報を全て表示することができます。
cf. 1. Schema を構築する - Apollo Basics - Apollo GraphQL DocsReserved Names
Types and fields required by the GraphQL introspection system that are used in the same context as user-defined types and fields are prefixed with "__
" two underscores. This in order to avoid naming collisions with user-defined GraphQL types.
Otherwise, any Name within a GraphQL type system must not start with two underscores "__
".
cf. Introspection | GraphQL
__
から始まる名前で定義された GraphQL chema の構成などがどうなっているのかを見ることができるようにするための機能っぽい
Schema を作成する
Schema は実際の GraphQL server かサーバーのスキーマファイルを指定します
- Express で簡易な GraphQL Server を作成して試してみる
- GraphQL のスキーマファイルを作成して試してみる
※ codegen.yml
の documents
の指定は削除しておく
1. Express で簡易な GraphQL Server を作成する
/api
ディレクトリを作成して Express (TypeScript) で簡易な GraphQL サーバーを作成し Code Generator を試してみます
構成
/api |- server.ts # Express server |- schema.ts # GraphQL の Schema を定義 |- teconfig.json
必要なパッケージのインストール
$ npm i express graphql express-graphql
$ npm i -D typescript @types/express @types/node ts-node-dev
teconfig.json
(npx tsc --init
したものに少し手を入れました)
{ "compilerOptions": { "target": "ES2018", "module": "commonjs", "lib": ["es2018"], "strict": true, "noImplicitAny": true, "strictNullChecks": true, "noImplicitThis": true, "alwaysStrict": true, "noUnusedLocals": false, "noUnusedParameters": false, "noImplicitReturns": true, "noFallthroughCasesInSwitch": false, "inlineSourceMap": true, "inlineSources": true, "experimentalDecorators": true, "strictPropertyInitialization": false, "esModuleInterop": true, "forceConsistentCasingInFileNames": true, "skipLibCheck": true } }
package.json
にサーバーを開始する npm script を追加
{
"scripts": {
+ "server": "ts-node-dev server.ts"
}
}
GraphQL のスキーマを定義
簡単な TODO リストを返すスキーマを定義しました
schema.ts
import { buildSchema } from 'graphql'; export const schema = buildSchema(` type Query { hello: String todos: [Todo] todo(id: String): Todo } type Todo { id: String! title: String! } `); export type TodoType = { id: string; title: string; };
Express GraphQL Server
作成したスキーマが問題なく動作しているか確認できるように GraphiQL を起動できるようにします
server.ts
import express from 'express'; import { graphqlHTTP } from 'express-graphql'; import { schema, TodoType } from './schema'; // ダミーデータ const Todos: TodoType[] = [ { id: '1', title: 'todo1', }, { id: '2', title: 'todo2', }, { id: '3', title: 'todo3', }, ]; // GraphQL の Resolver const root = { hello: () => { return 'Hello world!'; }, todo: ({ id }: { id: string }) => { const todo = [...Todos].find((todo) => todo.id === id); console.log(todo, id); return todo; }, todos: () => { return [...Todos]; }, }; const app = express(); app.use( '/graphql', graphqlHTTP({ schema, rootValue: root, graphiql: true, }) ); app.listen(4000); console.log('Running a GraphQL API server at http://localhost:4000/graphql');
動作確認
$ npm run server Running a GraphQL API server at http://localhost:4000/graphql
http://localhost:4000/graphql
にアクセスして GraphiQL の画面が表示され Document に定義したスキーマが表示されていればOK
GraphQL サーバーを使って GraphQL Code Generator を実行する
設定ファイルの schema に作成した GraphQL サーバーを指定
codegen.yml
overwrite: true - schema: "http://localhost:4000" + schema: "http://localhost:4000/graphql" generates: src/generated/graphql.d.ts: plugins: - "typescript" - "typescript-operations" - "typescript-graphql-files-modules" ./graphql.schema.json: plugins: - "introspection"
GraphQL Code Generator で型ファイルの生成
※ 予め GraphQL サーバーを起動しておく
$ npm run codegen > graphql-codegen@1.0.0 codegen > graphql-codegen --config codegen.yml ✔ Parse configuration ❯ Generate outputs ❯ Generate src/generated/graphql.d.ts ✔ Parse configuration ✔ Generate outputs
./graphql.schema.json
と ./src/generated/graphql.d.ts
が生成されていればOK!
graphql.d.ts にスキーマの方が TypeScript の型として出力されている
./src/generated/graphql.d.ts
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]> }; /** All built-in and custom scalars, mapped to their actual values */ export type Scalars = { ID: string; String: string; Boolean: boolean; Int: number; Float: number; }; export type Query = { __typename?: 'Query'; hello?: Maybe<Scalars['String']>; todo?: Maybe<Todo>; todos?: Maybe<Array<Maybe<Todo>>>; }; export type QueryTodoArgs = { id?: InputMaybe<Scalars['String']>; }; export type Todo = { __typename?: 'Todo'; id: Scalars['String']; title: Scalars['String']; };
2. GraphQL のスキーマファイルを作成する
local server を用意せず schema の定義ファイルから型生成を行う
構成
/ |-/src |- codegen.yml |- package.json |- schema.graphql # GraphQL サーバーのスキーマ
GraphQL サーバーのスキーマを定義
Express サーバーのものと同じ簡単な TODO リストを返すスキーマを定義する
./schma.graphql
(サーバーがない場合はどこに置くのが良いのだろう🤔)
type Query { hello: String } type Todo { id: String! title: String! } type Query { todos: [Todo] } type Query { todo(id: String): Todo }
GraphQL Code Generator の schema にスキーマファイルのパスを指定する
codegen.yml
overwrite: true - schema: 'http://localhost:4000/graphql' + schema: './schema.graphql' generates: src/generated/graphql.d.ts: plugins: - 'typescript' - 'typescript-operations' - 'typescript-graphql-files-modules' ./graphql.schema.json: plugins: - 'introspection'
GraphQL Code Generator で型ファイルの生成
$ npm run codegen > graphql-codegen@1.0.0 codegen > graphql-codegen --config codegen.yml ✔ Parse configuration ✔ Generate outputs
./graphql.schema.json
と ./src/generated/graphql.d.ts
が生成されていればOK!
Document の指定
document はクライアントから呼ぶ query なので暫定的に /src/graphql
ディレクトリ内に .graphql
ファイルで作成する
構成
/ |- /src | |- /graphql | |- getTodo.graphql | |- hello.graphql |- codegen.yml |- package.json |- schema.graphql
schema.graphql
type Query { hello: String } type Todo { id: String! title: String! } type Query { todos: [Todo] } type Query { todo(id: String): Todo }
graphql.document (クライアントから実行する query ) の作成
/src/graphql/getTodo.graphql
query GetTodos { todos { id title } } query GetTodo($id: String) { todo(id: $id) { id title } }
/src/graphql/hello.graphql
query SeyHello { hello }
GraphQL Code Generator の document を指定
documents
にクエリファイルのパスを指定する
codegen.ymll
overwrite: true
schema: './schema.graphql'
+ documents: './src/graphql/**/*.graphql'
generates:
src/generated/graphql.d.ts:
plugins:
- 'typescript'
- 'typescript-operations'
- 'typescript-graphql-files-modules'
./graphql.schema.json:
plugins:
- 'introspection'
GraphQL Code Generator で型ファイルの生成
$ npm run codegen > graphql-codegen@1.0.0 codegen > graphql-codegen --config codegen.yml ✔ Parse configuration ✔ Generate outputs
./graphql.schema.json
と ./src/generated/graphql.d.ts
が生成されることを確認。
./src/generated/graphql.d.ts
内に クライアントから呼び出す想定のクエリ (GraphQL.document
) の型が含まれていればOK
./src/generated/graphql.d.ts
// … 略 export type GetTodosQueryVariables = Exact<{ [key: string]: never }>; export type GetTodosQuery = { __typename?: 'Query'; todos?: Array<{ __typename?: 'Todo'; id: string; title: string; } | null> | null; }; export type GetTodoQueryVariables = Exact<{ id?: InputMaybe<Scalars['String']>; }>; export type GetTodoQuery = { __typename?: 'Query'; todo?: { __typename?: 'Todo'; id: string; title: string } | null; }; export type SeyHelloQueryVariables = Exact<{ [key: string]: never }>; export type SeyHelloQuery = { __typename?: 'Query'; hello?: string | null }; declare module '*/getTodo.graphql' { import { DocumentNode } from 'graphql'; const defaultDocument: DocumentNode; export const GetTodos: DocumentNode; export const GetTodo: DocumentNode; export default defaultDocument; } declare module '*/hello.graphql' { import { DocumentNode } from 'graphql'; const defaultDocument: DocumentNode; export const SeyHello: DocumentNode; export default defaultDocument; }
終えてみて
GraphQL Code Generator で TypeScript の型生成すると幸せになれる。と聞いていましたがイマイチやり方が判らず放置していたので今回時間を作って着手できてよかったです。
やはりまずは公式ドキュメントをよく読んで、プレーンなチュートリアルをやってみること。次にプラグインなどのカスタマイズをして、その時々のエラーをひとつひとつ解決していくこと。という地道な方法が一番理解できるな〜と感じました。
今週のアイカツ格言
📝 Tips
schema が URL の時 GraphQL
が動作している URL を指定しないとエラー
server.ts
app.use( '/graphql', graphqlHTTP({ schema, rootValue: root, graphiql: true, }) ); app.listen(4000);
codegen.yml
overwrite: true schema: 'http://localhost:4000' generates: src/generated/graphql.ts:
graphqlHTTP
は /graphql
に割り当てられているので、schema にローカルサーバーのルートである http://localhost:4000
を指定しているとエラーになる
Run codegen
$ npm run codegen > graphql-codegen@1.0.0 codegen > graphql-codegen --config codegen.yml ✔ Parse configuration ❯ Generate outputs ❯ Generate src/generated/graphql.d.ts ✖ Load GraphQL schemas → Failed to load schema Load GraphQL documents Generate ❯ Generate ./graphql.schema.json ✖ Load GraphQL schemas → Failed to load schema Load GraphQL documents Generate Something went wrong Failed to load schema for "./graphql.schema.json" Failed to load schema from http://localhost:4000/: Unexpected token < in JSON at position 0 SyntaxError: Unexpected token < in JSON at position 0 at JSON.parse (<anonymous>)
=> Failed to load schema from http://localhost:4000/:
👇 graphqlHTTP
が動作しているURLにすればOK
codegen.yml
overwrite: true - schema: 'http://localhost:4000' + schema: 'http://localhost:4000/graphql' generates: src/generated/graphql.ts:
Run codegen
$ npm run codegen > graphql-codegen@1.0.0 codegen > graphql-codegen --config codegen.yml ✔ Parse configuration ❯ Generate outputs ❯ Generate src/generated/graphql.ts ✔ Parse configuration ✔ Generate outputs
typescript-graphql-files-modules
プラグインを使う場合、生成する型ファイルの拡張子が .d
の形でないとエラーになる
codegen.yml
overwrite: true schema: './schema.graphql' generates: # 生成するファイルの拡張子が `.d` でない場合 src/generated/graphql.ts: plugins: - 'typescript' - 'typescript-operations' - 'typescript-graphql-files-modules' ./graphql.schema.json: plugins: - 'introspection'
Run codegen
$ npm run codegen > graphql-codegen@1.0.0 codegen > graphql-codegen --config codegen.yml ✔ Parse configuration ❯ Generate outputs ❯ Generate src/generated/graphql.ts ✔ Load GraphQL schemas ✔ Load GraphQL documents ✖ Generate → Unable to find template plugin matching typescript-graphql-files-modules ✔ Generate ./graphql.schema.json Something went wrong Plugin "typescript-graphql-files-modules" validation failed: for "src/generated/graphql.ts" Plugin "typescript-graphql-files-modules" requires extension to be ".d.ts"!
=> Plugin "typescript-graphql-files-modules" requires extension to be ".d.ts"!
👇 codegen.yml
overwrite: true schema: './schema.graphql' generates: # .d.ts に変更 - src/generated/graphql.ts: - src/generated/graphql.d.ts: plugins: - 'typescript' - 'typescript-operations' - 'typescript-graphql-files-modules' ./graphql.schema.json: plugins: - 'introspection'
Run codegen
$ npm run codegen > graphql-codegen@1.0.0 codegen > graphql-codegen --config codegen.yml ✔ Parse configuration ✔ Generate outputs
[参考]
- Installing Codegen | GraphQL Code Generator
- GraphQL Code Generator 導入する
- `yarn graphql-codegen init`をとりあえず実行してみた - Qiita
- 【GraphQL】GraphQL Code Generatorについてのメモ - とーますメモ
- Running an Express GraphQL Server | GraphQL
- GraphQL Code Generator で TypeScript の型を自動生成する - クックパッド開発者ブログ
- DBのスキーマとは何か?DBごとの違い - Qiita
- glob(グロブ):Dev Basics/Keyword - @IT