Next.js App RouterのfetchでGraphQLを叩くためにgql.tadaを使う
弊社(トラストハブ)では、ほとんどのプロダクトでGraphQL APIを採用しています。
これまで弊社では、Next.js Page RouterからGraphQL APIにリクエストするときは、urqlやApollo ClientなどのGraphQL専用クライアントを利用してきました。
そんな中、新たなプロジェクトでNext.jsを採用するにあたり、App Routerでプロダクトを開発することとなりました。
Next.js App Routerでは、fetchを使うことでキャッシュを利用することができ、ユーザー体験を大幅に改善できるという特徴があります。これについて、世の中のドキュメントやサンプルコードではREST APIを用いたものが多いのですが、GraphQL APIでもfetchを使うことで、キャッシュの恩恵を受けたいと考えました。
ただ、REST APIを叩く時と同じようにfetchをそのまま使うと、GraphQLで型安全に開発する、という体験ができません。
これについては従来であれば、GraphQL-Codegenを使って、GraphQLスキーマからTypeScriptの型を生成するセットアップが用いられてきました。しかし、このセットアップはQuery, Mutationの記述時にcodegenコマンドを毎回実行する必要があり、運用に少し手間がかかるという課題がありました。
gql.tada
そこで、App Router + GraphQL環境において、簡単なセットアップを実現するためにプロジェクトに投入したのがgql.tadaです。
gql.tadaの特徴は、純粋なTypeScriptの型システムと型チェッカーを利用して、エディターからの即時フィードバック、自動補完、型ヒントを含む、自動入力されたGraphQLドキュメントを利用できるというものです。
画像はGithub https://github.com/0no-co/gql.tada より
セットアップ
TypeScript 5.5以上であれば、gql.tadaをpackageに追加し、tsconfig.jsonに追加することで準備ができます。
npm install gql.tada
{
"compilerOptions": {
"strict": true,
"plugins": [
{
"name": "gql.tada/ts-plugin",
"schema": "./schema.graphql",
"tadaOutputLocation": "./src/graphql-env.d.ts"
}
]
}
}
VSCodeを利用している場合は、グローバルなTypeScriptバージョンをロードしてしまう可能性があります。そのため、.vscode/settings.jsonにローカルのTypeScriptバージョンを使用する設定を入れた方が良いです。
{
"typescript.tsdk": "node_modules/typescript/lib",
"typescript.enablePromptUseWorkspaceTsdk": true
}
スキーマの設定
先ほどtsconfig.jsonで "schema": "./schema.graphql",
という設定を追加しました。
これは .graphql
などの利用したいGraphQL APIのスキーマファイルを参照する必要があるため、ファイルをコピーしてくるなどしましょう。別リポジトリにスキーマファイルがある場合は、ghコマンドを使ってうまいことファイルを取り込む仕組みを作ると便利です。
また、pathの代わりにIntrospectionが有効なGraphQL APIを指定することで代用することもできます。それ以外にも、CLIでスキーマをダウンロードするオプションもあります。お好みに合わせて設定しましょう。
型の出力先
先ほどtsconfig.jsonにおける "tadaOutputLocation": "./src/graphql-env.d.ts"
は、TypeScriptプラグインによって型ファイルが出力されるpathになっています。こちらも自分のプロジェクトに合わせて設定しましょう。
GraphQLを書く
セットアップが完了した状態で、gql.tadaからgraphql関数をimportしてGraphQLを書くことで、型の支援を受けることができます。Queryを変更すると、即時にResponseの型も反映されるので、効率よく開発することができます。
import { graphql } from 'gql.tada';
const PokemonsQuery = graphql(`
query PokemonsList($limit: Int = 10) {
pokemons(limit: $limit) {
id
name
}
}
`);
型を取り出してコードの中で利用したいときは、ResultOf, VariablesOfを利用することができます。
import { graphql, ResultOf, VariablesOf } from 'gql.tada';
const MarkCollectedMutation = graphql(`
mutation MarkCollected($id: ID!) {
markCollected(id: $id) {
id
name
collected
}
}
`);
type variables = VariablesOf<typeof MarkCollectedMutation>;
type result = ResultOf<typeof MarkCollectedMutation>;
複数のスキーマがある場合
gql.tadaは複数のGraphQL APIを一つのクライアントから叩く場合にも対応しています。
具体的には、tsconfig.jsonのschemaを複数設定して、
{
"compilerOptions": {
"plugins": [
{
"name": "gql.tada/ts-plugin",
"schemas": [
{
"name": "pokemon",
"schema": "./graphql/pokemon.graphql",
"tadaOutputLocation": "./src/graphql/pokemon-env.d.ts"
},
{
"name": "simple",
"schema": "./graphql/simple.graphql",
"tadaOutputLocation": "./src/graphql/simple-env.d.ts"
}
]
}
]
}
}
各スキーマごとにgraphql関数をラップしたファイルを作成します。以下は上のスキーマにおけるsimpleと名付けたスキーマを対象としたgraphql関数になります。この際、introspectionとして、それぞれのスキーマの型ファイルを明示的に設定する必要があります。また、exportする時の名前は graphql
という名前のままにしておかないと、VSCodeのシンタックスハイライトを利用できなくなる点に注意しましょう。
import { initGraphQLTada } from 'gql.tada';
import type { introspection } from './simple-env.d.ts';
export const graphql = initGraphQLTada<{
introspection: introspection;
}>();
利用する際には、import先にラップしたgraphql関数を呼び出すことで、それぞれのスキーマごとの型を利用することができます。
import { graphql } from './graphql/simple';
const query = graphql(`
query Test { helloWorld }
`);
まとめ
gql.tadaを新たに採用したことで、codegenよりも開発体験が良い状態でGraphQLを扱えるようになりました。
また、RSCとの組み合わせも問題なく動作しており、キャッシュによる高速化の恩恵を得ることができています。
ここでは説明していませんが、Fragmentなどの機能も利用できますので、ぜひドキュメントを眺めてみてください。
今後、既存プロジェクトもPage RouterからApp Routerへ徐々に移行していくと思うので、その際にはgql.tadaを活用して進めていきたいと思います。
おまけ
私が働いているトラストハブ社は、今週17億円の資金調達を実施したことを発表しました。これに伴い、エンジニア採用を積極的に進めようとしています。会社紹介スライドはこちらです。
カジュアル面談などご興味のある方は、ぜひXのDMなどからお気軽に声をかけてください。
さらにおまけ: Xやっています。ぜひお友達になってください → https://x.com/jaqk_and
Discussion