2026年10月8日に、DataLoaderの現行公式READMEを再読しました。READMEはGraphQLのフィールド解決で個別に発生しやすいデータ取得をbatchし、同一リクエスト内で同じキーをメモ化する例を示しています。mutation後に古い値を使わないためclearが必要な場合や、長寿命インスタンス・利用者別権限ではキャッシュを共有しない注意もあります。これはN+1を検討する部品の説明であり、GraphQLの認可、HTTPキャッシュ、要求量制限、実サービス性能を保証する資料ではありません。
30秒で要点
GraphQLは、APIで取得したいデータの形を指定する問い合わせ言語と、その問い合わせを実行する仕組みです。利用者がデータベースへ自由にアクセスする機能ではありません。サーバーが公開したスキーマの範囲でフィールドを選び、サーバー側の処理が値を返します。
- 画面ごとに必要なデータが異なる場合、取得するフィールドを明示できます。
- 型、認可、データ取得の効率は別の問題です。スキーマだけでは解決しません。
- 小さな実行例で振る舞いを確かめた後、通信、権限、負荷、変更管理を設計します。
この記事は、APIの基本を知り、GraphQLを採用する理由と運用負担を判断したい開発者向けです。
スキーマ、問い合わせ、実行は役割が違う
スキーマは、公開する型、フィールド、引数を定める契約です。問い合わせは、その契約から今回使うフィールドを選びます。リゾルバーは値を返す処理であり、メモリ上の値、データベース、別のAPIなどに接続できます。
商品画面で名前だけを使うなら、価格や説明を要求しない選択ができます。ただし、返すJSONが小さくなることと、裏側のデータベース処理が軽くなることは別です。リゾルバーが毎回全列を取得していれば、サーバー内部の仕事はあまり減りません。
型が表す約束
String、Int、Boolean、IDなどのスカラーと、それらを組み合わせたオブジェクト型を使います。IDは識別子であり、数値計算用の型とは分けて考えます。
String!は、その位置でnullを返さない約束です。[Product!]!ならリスト自体も要素もnullにできませんが、空配列は許されます。必須にすれば品質が上がるとは限りません。外部サービスの障害時に何を欠損として表示できるかも考えて決めます。スキーマと型の公式説明で、この違いを確認できます。
表が収まらない場合は、横にスクロールできます
| 操作 | 役割 | 別途決めること |
|---|---|---|
| Query | データの読み取り | ページ分割、取得量、認可、キャッシュ |
| Mutation | 状態を変える操作の入口 | 検証、保存、競合、重複実行、失敗時の扱い |
| Subscription | 変化に応じた結果の配信 | 通信方式、購読権限、切断後の復元、資源管理 |
Mutationを定義しても、データベースのトランザクションや重複防止が自動で実装されるわけではありません。Subscriptionも、それだけでイベント保存や再接続後の欠落復元を保証しません。
HTTPサーバーを作らず、選択と検証を確かめる
次の例は架空の商品1件を扱います。GraphQL.jsの実行関数を直接呼ぶため、ポートを開かず、データベースも使いません。検証環境はNode.js 24.18.0、GraphQL.js 17.0.2です。導入時は公式の実行環境条件も確認してください。
空の作業ディレクトリで次を実行します。既存プロジェクトの依存関係へ混ぜずに試せます。
npm init -y
npm install --save-exact graphql@17.0.2
同じ場所にexample.mjsを保存します。
import { buildSchema, graphql } from 'graphql';
const schema = buildSchema(`
type Product {
id: ID!
title: String!
stock: Int
}
type Query {
product(id: ID!): Product
}
`);
const rootValue = {
product: ({ id }) => id === 'p1'
? {
id,
title: 'Notebook',
stock: () => { throw new Error('Stock unavailable'); },
}
: null,
};
const source = `
query ProductCard($id: ID!) {
product(id: $id) { id title }
}
`;
const result = await graphql({
schema,
source,
rootValue,
variableValues: { id: 'p1' },
});
console.log(JSON.stringify(result));
node example.mjsを実行すると、data.productにidとtitleが入ります。stockは選択していないので返りません。値は問い合わせ文字列へ連結せず、変数として渡しています。
同じファイルを次のように変更し、結果を比較してください。変更は1つずつ戻して試します。
表が収まらない場合は、横にスクロールできます
| 変更 | 確認する結果 | 分かること |
|---|---|---|
変数のidをmissingにする | product: null | 見つからない場合の表現を決められる |
選択へunknownFieldを加える | 検証エラー。dataなし | 未定義フィールドは実行前に拒否される |
選択へstockを加える | stock: nullとerrors | 一部の失敗でも他の値が残り得る |
stock: Int!にしてstockを選ぶ | product: nullとerrors | 非nullの約束を破ると上位へ影響する |
例のエラーメッセージは説明用です。本番では内部のSQL、秘密情報、スタックを利用者へ返さないようにします。また、存在しない商品と閲覧権限のない商品をどう表現するかも、情報漏えいを避ける観点で決めます。
この例で確認できるのは、スキーマに対する検証とローカル実行です。公開APIに必要なHTTP処理、認証、タイムアウト、永続化、監視は含みません。
成功と失敗を、画面の状態へ結び付ける
GraphQLの応答はdataだけでなくerrorsを含む場合があります。問い合わせ自体が不正なら、通常は実行前に失敗します。実行中の一部の失敗なら、残りのデータを表示できる場合があります。公式の応答の説明は、この2種類を区別しています。
商品名は表示できても在庫を取得できない場合、在庫を0と見なすのは誤りです。「在庫を確認できません」と示し、購入可否は注文処理側で再確認します。通信に成功したこと、データを取得できたこと、購入が確定したことを分ける必要があります。
Reactなどの画面に接続するときも、送信ボタンを押した瞬間にフォームを消さず、保存結果を確認してから状態を変更します。クライアントのキャッシュを使う場合は、識別子、更新後の反映、ログアウト時の個人データの除去も設計対象です。ライブラリの導入だけで業務上の整合性は保たれません。
RESTとの比較は、画面と運用の課題から始める
GraphQLは、複数の画面やクライアントが同じ領域のデータを異なる形で利用するときに候補になります。一方、用途が固定された小さなAPIやファイル配信を、形式を統一するためだけに置き換える必要はありません。
表が収まらない場合は、横にスクロールできます
| 判断する点 | GraphQLを検討しやすい状況 | RESTなどで十分な状況 |
|---|---|---|
| 応答の形 | 画面ごとに必要な関連データが大きく違う | 少数の用途に応答が安定している |
| 変更の調整 | 型と利用中のフィールドを契約として管理したい | 明確なリソースと操作で契約を管理できる |
| 取得の効率 | 複数の取得を整理し、内部処理も最適化できる | 既存APIとキャッシュで目標を満たす |
| 運用能力 | クエリ別の費用・権限・使用状況を管理できる | 追加の制御面を保守する余力が少ない |
「RESTでは必要な項目を選べない」「GraphQLではHTTPキャッシュが使えない」といった二分法は避けます。RESTでもフィールド選択や集約APIを設計できます。GraphQLでもGET、キャッシュヘッダー、問い合わせの識別子を用いる構成があります。GraphQLの性能ガイドを参考に、公開データと個人別データのキャッシュを分離してください。
公開前に確認する4つの運用境界
1. 認可は、フィールドの型とは別に行う
認証で利用者を確認し、その利用者が対象データを読めるか、変更できるかを業務層で判断します。GraphQLと別のAPIが同じ業務処理を使うなら、どちらから呼ばれても同じ条件になる構成が必要です。公式の認可ガイドも業務層での判断を勧めています。
クライアントが渡したユーザーIDを、そのまま実行者と見なしてはいけません。スキーマのディレクティブに権限名を書いても、それを評価する処理がなければ制御になりません。
2. 少ない通信回数でも、裏側の取得は増え得る
注文一覧を1回取得し、各注文の顧客を1件ずつ取得すると、注文数に応じて呼び出しが増えます。これがN+1問題の典型です。まとめて取得するバッチ処理、適切なJOIN、リクエスト内の重複排除などを検討します。
DataLoaderを使う場合、利用者ごとに見える情報が異なるなら、一般にリクエスト単位でインスタンスを作ります。全利用者で同じキャッシュを共有する例を、そのまま認証付きAPIへ持ち込まないでください。DataLoaderの公式READMEは、リクエスト間での誤ったデータ共有を注意点として説明しています。
3. 問い合わせの自由度に、資源の上限を付ける
一覧件数、深さ、フィールドの広がり、別名での反復、複数操作のまとめ送りを評価します。深さだけを制限しても、浅くて大量のデータを要求する問い合わせは残ります。利用者単位の速度制限、処理費用の見積もり、実行時間上限を組み合わせます。
管理下のクライアントなら、登録済みの問い合わせだけを許す方式も候補です。公開APIでは、第三者の利用方法も踏まえて制約を決めます。入力値の業務検証や出力の安全な表示も必要です。セキュリティガイドを基に、実際のデータ量で負荷を測ってください。
4. 変更と障害を、操作単位で追えるようにする
スキーマを変更する前に、利用中のフィールドと古いクライアントを確認します。非推奨の案内は移行期間を作る手段であり、古い利用者を無視して削除できる印ではありません。
監視ではHTTPステータスだけでなく、操作名、GraphQLエラー、取得先の時間、応答サイズを見ます。変数や応答全体には個人情報が含まれ得るため、記録項目は限定します。ファイル本体のアップロードなどは、GraphQLで扱うメタデータと、別の転送経路を使う部分を分けて検討できます。
導入時は1画面と1つの業務領域を選び、既存方式と同じ権限条件・データ量で比較すると判断しやすくなります。応答の形を整える利点が、追加する契約管理と運用の負担に見合うかを確認してください。