GraphQL

GraphQLとは?読み方・REST APIとの違いとクエリ・リゾルバの書き方をわかりやすく解説

GraphQLとは?読み方・REST APIとの違いとクエリ・リゾルバの書き方をわかりやすく解説

GraphQL(グラフキューエル)とは、クライアントが「どの形のデータが欲しいか」をクエリで書き、サーバーがその形どおりにJSONを返すAPIのクエリ言語です。Facebook(現Meta)が2012年から社内で使い、2015年に仕様を公開しました。この記事では、REST APIとの違いを押さえたうえで、引数・エイリアス・@includeを使ったクエリの条件指定、リゾルバの仕組みとN+1問題、Node.jsで動かす最小構成までを、手元で実行した結果つきで説明します。版番号と制限値は2026年10月27日時点で公式情報を確認した値です。

まとめ:GraphQLは欲しいデータの形をクライアントが書くAPIのクエリ言語

GraphQLのAPIは原則1つのエンドポイントで、クライアントがフィールドを選んで問い合わせます。RESTで起きやすい「使わない項目まで返る(オーバーフェッチ)」「足りずに何度も呼ぶ(アンダーフェッチ)」を減らせるのが一番の利点です。

  • 要素はスキーマ(型の定義)、クエリ(取得)、ミューテーション(更新)、リゾルバ(値を返す関数)の4つ
  • 条件は引数で指定し、変数・エイリアス・@include/@skipで出し分ける
  • 代償はHTTPキャッシュの効きにくさ、N+1問題、重いクエリによる負荷。DataLoaderと深さ制限で抑える
  • Postmanの2025年調査ではAPI開発者の33%がGraphQLを利用(RESTは93%)。置き換えではなく併用が主流

Webとモバイルなど複数の画面からデータを組み合わせて取るシステムなら採用、単純なCRUDやCDNキャッシュ頼みの公開APIならRESTで十分です。以下、仕組みから順に見ていきます。

GraphQLの意味と読み方、Facebook発の成り立ち

読み方は「グラフキューエル」で、Graph Query Languageの略です。データを型どうしのつながり(グラフ)として捉え、クエリでたどって必要な部分だけを取り出す、という発想から名付けられました。グラフデータベース専用の言語ではありません。

1つのエンドポイントで必要なフィールドだけを返す仕組み

RESTでは/users/1と/users/1/postsのようにリソース別のURLを使う設計が一般的ですが、返す項目や関連データを引数で選べるAPIもあります。GraphQLは通常/graphqlの1か所に問い合わせ、返してほしいフィールドをクライアントが列挙します(公式ガイド Serving over HTTP)。画面に名前と投稿タイトルしか出さないなら、その2つだけを書けば応答もその2つだけになり、クエリはスキーマ(型の定義)で検証されてから実行されます。

2012年のFacebook社内利用からGraphQL Foundation設立までの経緯

公式サイトによると、Facebookのモバイルアプリは2012年からGraphQLで動いており、仕様は2015年にオープンソース化されました(graphql.org)。画面ごとに必要なデータが違うモバイルで、RESTの往復回数と転送量が問題になったことが開発の動機です。2018年11月6日にはLinux FoundationがGraphQLを支える財団の設立を発表し(発表記事)、以後の仕様策定は特定企業から切り離されたGraphQL Foundationで進んでいます。

スキーマ・クエリ・ミューテーションの基本と最小のコード例

ユーザーと投稿を扱う小さな例で書き分けます。リアルタイム配信のサブスクリプションも操作の1つで、実装はGraphQLサブスクリプションとgraphql-wsでの実装で扱っています。

スキーマで型とエントリポイントを定義する書き方

type Query {
  user(id: ID!): User
}

type User {
  id: ID!
  name: String!
  posts(first: Int = 10): [Post!]!
}

type Post {
  title: String!
}

Query型が問い合わせの入口で、!はnullを許さない型、[Post!]!は「空配列は許すが、配列自体も各要素もnullにはできない投稿の配列」です。postsには件数を受け取る引数firstを付け、既定値を10にしています。null許容や命名の決め方で後の運用が大きく変わるため、設計の判断はGraphQLスキーマ設計のベストプラクティスにまとめています。

クエリで必要なフィールドだけを指定する書き方

query GetUser {
  user(id: "1") {
    name
    posts {
      title
    }
  }
}

応答は{"data":{"user":{"name":"山田","posts":[...]}}}の形で返り、スキーマにあるidは書いていないので含まれません。同じフィールドの組を複数のクエリで使い回すならGraphQLのFragment(フラグメント)で部品化できます。

ミューテーションと変数でデータを更新する書き方

mutation CreatePost($input: CreatePostInput!) {
  createPost(input: $input) {
    title
  }
}

# variables(クエリとは別のJSONで送る)
{ "input": { "userId": "1", "title": "はじめての投稿" } }

更新はミューテーションで書き、値はクエリ本文に埋め込まず変数として別に渡します(公式 Mutations)。本文が固定されるので、後述の信頼済みドキュメント(許可済みクエリだけを実行する方式)とも組み合わせやすくなります。なお、この例はMutation.createPostとCreatePostInput、更新用リゾルバを別途定義したAPI向けの構文例で、本記事の取得専用スキーマと後述のserver.mjsでは実行できません。

クエリの条件指定:引数・変数・エイリアス・@include/@skipの書き方

「GraphQLで条件を付けて取りたい」ときに使う仕組みは4つあり、どれもSeptember 2025版の仕様で定義されています。SQLのWHERE句のような汎用の検索構文は仕様に無く、どんな条件を受け付けるかはスキーマ側が引数として決めます。

引数でIDや件数を指定して絞り込む書き方

仕様は、フィールドを「値を返す関数」と見なし、振る舞いを変える引数を受け取れると定めています(§2.7 Arguments)。user(id: "1")のIDも、posts(first: 1)の件数も引数です。下は先のスキーマに対して投稿を1件だけ取るクエリで、手元で実行すると{"data":{"user":{"name":"山田","posts":[{"title":"はじめての投稿"}]}}}が返りました。

{
  user(id: "1") {
    name
    posts(first: 1) {
      title
    }
  }
}

件数を引数で制限できるようにしておくのは、重いクエリ対策の前提でもあります。GitHubのGraphQL APIは一覧(connection)にfirstかlastを必須とし、値を1〜100に制限しています(Rate limits and query limits)。2ページ目以降の取り方はカーソル方式が定番で、選び方はページネーション設計(オフセットとカーソルの選び分け)で比較しています。

エイリアスで同じフィールドを別条件で並べて取得する書き方

同じuserフィールドを引数違いで2回呼ぶと、応答のキーが衝突します。エイリアスは応答側の名前を付け替える仕組みです(§2.8 Field Alias)。

{
  yamada: user(id: "1") { name }
  sato: user(id: "2") { name }
}

実行結果は{"data":{"yamada":{"name":"山田"},"sato":{"name":"佐藤"}}}で、1回のリクエストで2人分を取れました。ただしエイリアスを大量に並べれば1リクエストで何百回もリゾルバを呼べるため、本番ではエイリアス数にも上限を設けます。

変数と@include・@skipで項目の出し入れを切り替える書き方

画面の状態によって取る項目を変えたいときは、変数とディレクティブを組み合わせます。@include(if: 真偽値)は真のときだけ、@skip(if: 真偽値)は真のときだけ除いてフィールドを実行します(§3.13.2 @include)。

query GetUser($id: ID!, $withPosts: Boolean!) {
  user(id: $id) {
    name
    posts @include(if: $withPosts) {
      title
    }
  }
}

# variables
{ "id": "1", "withPosts": false }

withPostsをfalseで送ると、応答は{"data":{"user":{"name":"山田"}}}となりpostsは消えました。クエリ文字列を組み立て直さずに済むので、文字列連結でクエリを作るコードより安全です。

リゾルバの仕組み:4つの引数とN+1問題の実測

サーバーは届いたクエリを構文解析し、スキーマで検証し、フィールドごとにリゾルバを呼んで値を集めます(公式 Execution)。柔軟さも性能問題も、この「フィールド単位で関数が呼ばれる」構造から生まれます。

obj・args・context・infoの4引数が受け取る値

リファレンス実装のリゾルバは4つの引数を受け取ります。

引数 中身 使いどころ
obj(parent) 親フィールドの戻り値 User.postsでユーザーIDを得る
args クエリで渡された引数 first・idの値
context 全リゾルバ共通の値 ログイン中のユーザー・DB接続
info 実行中のフィールドとスキーマの情報 高度な最適化

認可の判定に使うログインユーザーはcontextに入れて全リゾルバへ配るのが基本形です。GraphQL.jsでリゾルバを省略すると、既定のリゾルバが親オブジェクトの同名プロパティを読み取ります。その値が関数なら、引数・context・infoを渡して呼び出した結果を使います。

100件の一覧で101回の問い合わせになるN+1問題とDataLoader

100人のユーザーとそれぞれの投稿を取るクエリを、graphql 17.0.2と模擬DB関数で実行し、データ取得関数の呼び出し回数を数えました。以下の回数は実DBのSQL発行数や通信時間の実測ではありません。User.postsのリゾルバがユーザーごとに投稿を取りに行くと、一覧の1回+100人分で101回です。これがN+1問題です。

// User.posts のリゾルバ(parent, args, context, info)
resolve: (parent, args, context, info) =>
  context.loader.load(parent.id),

// リクエストごとに作り、同じ処理単位で集まったIDを1回で問い合わせる
const loader = new DataLoader(async (ids) => db.postsByUsers(ids));
// postsByUsersはidsと同じ長さ・順序の配列を返す。各要素はそのIDの投稿配列で、投稿なしは[]。
await graphql({ schema, source, contextValue: { loader } });

dataloader 2.2.3で同じ処理単位に集まったIDをまとめて渡すように変えると、回数は2回(ユーザー一覧1回+投稿の一括取得1回)に減りました。DataLoaderは結果をキャッシュするので、ユーザー間でデータが混ざらないようcontextに載せてリクエストごとに作り直します。ORM側で発行回数を検出する方法はN+1問題の発行クエリ数の実測と検出方法で解説しています。

REST APIとGraphQLの違い:比較表と同じデータの取得例

RESTはリソースごとのURLとHTTPメソッドで操作する設計スタイルで、原則はRESTとは?REST APIの仕組みと6原則で整理しています。GraphQLとの違いは次の7点です。

観点 REST API GraphQL
エンドポイント リソースごとに複数 原則1つ
返るデータ API設計次第で項目選択も可能 スキーマ内の項目を選択
関連データの取得 API設計次第で一括取得可能 スキーマ内で一括取得可能
HTTPキャッシュ GETで素直に効く POST中心で工夫が要る
エラーの表し方 HTTPステータス 応答のerrors配列が主
仕様書 OpenAPI等を別に用意 スキーマが仕様を兼ねる
版の管理 URLに/v2/を切ることが多い フィールド追加と非推奨化

RESTで「ユーザーとその投稿」を出すにはGET /users/1とGET /users/1/postsの2回に分けて取得する設計例を考えます。この例はGraphQLなら前章のGetUserクエリ1回にまとめられますが、RESTでも関連データを含めるエンドポイントを設計すれば1回で取得できます。

見落としやすいのはエラーの扱いです。GraphQLでは一部のフィールドが失敗しても、成功した分のdataと失敗理由のerrorsが同じ応答に入ります。GraphQL over HTTPの仕様案(2026年10月時点でStage 2: Draft)は、dataがnullでなければ2xxを返すことを必須としているため、HTTPステータスだけを見る監視では失敗を取りこぼします(Status Codes)。選定基準や移行コストまで含めた比較はREST APIとGraphQLの違いと使い分けで扱っています。

Node.jsでGraphQLサーバーを動かす最小手順:graphql 17とgraphql-http

公式リファレンス実装のgraphql(graphql-js)と、GraphQL over HTTPに沿ったサーバー部品graphql-httpで最小構成を作ります。2026年10月27日時点のnpmの最新はgraphql 17.0.2(2026年7月3日公開)とgraphql-http 1.23.1(2026年9月28日公開)です。graphql 17系はNode.js 22・24・25系または26以上(^22 || ^24 || ^25 || >=26)を要求するため、Node.js 20の環境では16系(最新16.14.2)に留めます。以下はNode.js 26.5.0で実行しました。

graphqlとgraphql-httpで書く約40行のserver.mjs

空のディレクトリでnpm init -yを実行し、package.jsonに"type": "module"を加えてからnpm install graphql@17 graphql-httpで依存を入れます。

import http from 'node:http';
import { buildSchema } from 'graphql';
import { createHandler } from 'graphql-http/lib/use/http';

// スキーマ(型と入口)
const schema = buildSchema(`
  type Query {
    user(id: ID!): User
  }
  type User {
    id: ID!
    name: String!
    posts(first: Int = 10): [Post!]!
  }
  type Post {
    title: String!
  }
`);

// データ(本番ではDBや他のAPIから取得する)
const users = {
  '1': { id: '1', name: '山田', posts: [{ title: 'はじめての投稿' }, { title: '2本目' }] },
  '2': { id: '2', name: '佐藤', posts: [] },
};

// ルートのリゾルバ:引数を受け取り、postsは件数を受け取る関数として返す
const rootValue = {
  user: ({ id }) => {
    const u = users[id];
    if (!u) return null;
    return { ...u, posts: ({ first }) => u.posts.slice(0, first) };
  },
};

const handler = createHandler({ schema, rootValue });
http.createServer((req, res) => {
  if (req.url.startsWith('/graphql')) return handler(req, res);
  res.writeHead(404).end();
}).listen(4000);

buildSchemaで作ったスキーマでは、rootValueやオブジェクトに置いた関数が引数付きで呼ばれます。前章のエイリアスや@includeの実行結果は、このサーバーに送って得たものです。

curlによるdata・errors・HTTPステータスの確認

node server.mjsで起動し、別のターミナルから送ります。

curl -s -w ' HTTP%{http_code}\n' http://localhost:4000/graphql \
  -H "Content-Type: application/json" \
  -H "Accept: application/graphql-response+json" \
  -d '{"query":"{ user(id: \"1\") { name posts { title } } }"}'

# 結果
{"data":{"user":{"name":"山田","posts":[{"title":"はじめての投稿"},{"title":"2本目"}]}}} HTTP200

スキーマに無いemailを指定すると、実行前の検証で弾かれ{"errors":[{"message":"Cannot query field \"email\" on type \"User\".", ...}]}だけが返りました。このときのHTTPステータスはAcceptで変わり、application/graphql-response+jsonでは400、application/jsonだけを送ると200でした。仕様案は検証失敗に422を推奨しているので、実装ごとに差がある点を監視設計で織り込みます。存在しないID(user(id: "9"))は検証を通るため、{"data":{"user":null}}がHTTP 200で返ります。

GraphQLのメリット・デメリットと実務での対処

往復回数・通信量・スキーマの契約で効く3つのメリット

  • 往復回数が減る:複数のRESTエンドポイントを順に呼んでいた画面を1クエリにまとめられる。回線の遅いモバイルほど効く
  • 通信量の無駄が減る:画面で使うフィールドだけを返す
  • スキーマが契約になる:フロントエンドとバックエンドが同じ型定義を見て並行開発でき、GraphQL Code GeneratorでTypeScriptの型も生成できる

フィールドの追加は既存のクエリを壊さず、古いフィールドは@deprecatedで段階的に退役させられるので、URLに版を切る運用も避けやすくなります。

HTTPキャッシュ・学習コスト・重いクエリというデメリット

エンドポイントが1つでPOST中心のため、CDNやブラウザのHTTPキャッシュはそのままでは効きません。公式のPerformanceは、queryをGETで送り、長いクエリ本文の代わりにハッシュを送る永続化クエリ(persisted queries)を併用する方法を示しています。クライアント側はIDごとにデータを正規化して持つキャッシュが定番で、Apollo Clientのキャッシュがその代表です。

もう1つは学習コストです。スキーマ設計、リゾルバ、N+1対策、クエリ制限について、チームの経験に応じた学習・設計時間を確保する必要があります。重いクエリ1本でサーバーが詰まる問題は、次のセキュリティ対策と同じ手段で抑えます。

GraphQLのセキュリティ対策:認可・信頼済みドキュメント・深さ制限の実装順

クエリを自由に書けることは、攻撃者にとっても自由に書けるということです。公式のSecurityに沿って、組み込む順に並べます。

  1. フィールド単位の認可:リゾルバかその手前のビジネスロジック層で、contextのユーザーに見せてよいかを判定する(Authorization)
  2. 信頼済みドキュメント:自社アプリ専用のAPIなら、事前登録したクエリのIDしか実行しない。未登録のクエリを拒否し、実行可能な操作を事前に審査した範囲へ限定できる
  3. 深さ・幅・複雑度の制限:ネストの段数、エイリアス数、1回に束ねるクエリ数に上限を設け、フィールドの重みで推定コストを計算して超えたら拒否する
  4. 重さで数えるレート制限とタイムアウト:GitHubのユーザー認証では原則1時間5,000ポイントで、一部のEnterprise Cloud関連の認証では10,000ポイントになる。1回の要求は50万ノードまでで、処理が10秒を超えると打ち切られる。方式の比較はAPIレート制限の5方式と429設計を参照
  5. イントロスペクションとエラー詳細の制限:自社専用なら本番でスキーマの問い合わせを止め、内部エラーの詳細は伏せる。公式は無効化だけでは隠しているにすぎないと注意している

認可と深さ・幅・複雑度の制限は公開前に入れます。信頼済みドキュメントは、実行するクエリを事前に管理できる自社アプリ専用APIで採用します。公開後に制限を足すと、既に動いているクライアントのクエリを弾くおそれがあります。

GraphQLのトレンド:利用率33%と2025年9月の仕様改訂

Postmanの2025 State of the API Reportでは、利用しているAPIの方式はRESTが93%、Webhooksが50%、WebSocketsが35%、GraphQLが33%でした。RESTを置き換える流れではなく、RESTを土台に画面向けやリアルタイム用途でGraphQLを足す形が主流です。商用APIでも、Shopifyは2024年10月1日にREST Admin APIをレガシー扱いとし、2025年4月1日以降の新規公開アプリにGraphQL Admin APIを必須にしました(Shopify REST Admin API)。

言語仕様は、2021年10月版以来の改訂となるSeptember 2025 Editionが2025年9月3日付で公開されています(spec.graphql.org)。公式ブログの発表が挙げた主な変更は次のとおりです。

  • OneOf入力オブジェクト:@oneOfを付けた入力型で、複数の候補のうち1つだけを指定させる(IDかメールアドレスのどちらかで検索、など)
  • Schema Coordinates:User.nameのようにスキーマ内の要素を一意に指す書き方の標準化
  • 実行ドキュメントへの説明文、非推奨化の対象拡大、Unicode全範囲への対応

リファレンス実装のgraphql-jsは2026年6月15日に17.0.0を出し、Node.js 20のサポートを外しました。複数チームのスキーマを1つに合成するGraphQLフェデレーションも大規模組織で広がっています。サーバーの実装はNode.jsならApollo ServerやGraphQL Yoga、PythonならStrawberry、JavaならSpring for GraphQL、Goならgqlgenが代表です。マネージドで始めるならAWS AppSync、既存DBからAPIを生成するならHasuraも候補になります。

GraphQLを採用する条件と見送る場面:受託開発での判断基準

複数クライアントで画面が多いシステムでの採用

Web・iOS・Androidのように複数のクライアントがあり、画面ごとに必要なデータの組み合わせが違うなら、GraphQLを採用します。画面ごとに専用エンドポイントを増やす設計では、画面追加のたびにAPIの実装・テスト・保守が必要になりやすいためです。複数の社内システムや外部SaaSを1つのAPIの裏にまとめたい場合も、スキーマを窓口にできるGraphQLが向きます。画面専用のAPI層を挟む構成との比較はBFFとAPI Gatewayの違い・採用判断で整理しています。

導入は全面置き換えより、既存のRESTを残したまま新しい画面向けにGraphQLの層を足す段階導入を勧めます。最初のスプリントでスキーマと認可の置き場所を決め、DataLoaderと深さ制限を入れてから画面を増やす順にすると、後から性能と安全性を直す手戻りが減ります。

単純なCRUDやCDNキャッシュ重視の公開APIでの見送り

次のどれかに当てはまるなら、GraphQLは過剰です。RESTで作ります。

  • 管理画面のように、1画面=1リソースの単純なCRUDが中心
  • 不特定多数に公開し、CDNのキャッシュで負荷を逃がしたい
  • ファイルのアップロード・ダウンロードが主役
  • チームにGraphQLの経験者がおらず、N+1対策とクエリ制限を設計する時間を取れない

よくある失敗は、新しいからという理由でGraphQLを選び、認可もクエリ制限もREST時代の感覚のまま公開してしまうことです。クライアントもサーバーもTypeScriptに閉じているなら、tRPCの方が軽く済む場合もあります。どちらで作るか決めきれない段階からでも、一創のAPI開発・システム連携で既存システムの構成を見たうえで方式を提案します。

よくある質問

GraphQLとSQLの違いは何ですか?

どちらもクエリ言語ですが、相手が違います。SQLはデータベースに対してテーブルの結合や集計を指示する言語で、主にサーバーの内部で使います。GraphQLはクライアントがAPIに対して欲しいデータの形を指示する言語で、裏でSQLを発行するか別のAPIを呼ぶかはリゾルバの実装次第です。GraphQLサーバーが内部でSQLを発行する組み合わせが典型的です。

GraphQLを使うにはグラフデータベースが必要ですか?

必要ありません。名前のGraphはデータのつながりを指していて、保存先の種類とは無関係です。MySQLやPostgreSQLなどのリレーショナルデータベースでも、既存のREST APIでも、リゾルバを書けばデータ源にできます。

GraphQLはREST APIの完全な代替ですか?

代替ではありません。GitHubは、外部の開発者向けにREST APIとGraphQL APIの両方を公開しています。公開範囲だけで方式を決めるのではなく、必要な操作やデータ取得の形に応じて選択・併用できます。Postmanの2025年調査でもRESTの利用が93%と大半を占めています。

GraphQL APIとは何を指しますか?

GraphQLのクエリを受け付けるAPIのことで、多くは/graphqlのような1つのURLでPOSTを受けます。GitHubならhttps://api.github.com/graphqlがそれにあたり、個人アクセストークンを付けて送れば手元にサーバーが無くても試せます(Forming calls with GraphQL)。

スキーマファーストとコードファーストの違いは?

スキーマファーストは先にSDLで型定義を書いて実装を合わせる方式で、仕様が明確になりフロントエンドと並行開発しやすくなります。コードファーストはプログラムの型やクラスからスキーマを生成する方式で、実装の変更に追従しやすいのが利点です。複数の職種がスキーマをレビューする体制ならスキーマファーストが向きます。

関連記事

お気に入りに入れた記事の一覧

この記事は以下の記事からリンクされています

ほか 12 件の記事からもリンクされています。

資料請求

今日のトレンド記事 直近 24 時間で、いつもより多く読まれている記事

  1. 2026.10.27 テックブログ Brotli 脆弱性:CVE-2026-111497の影響確認とNode.js・Python・.NETの更新手順
  2. 2026.10.28 テックブログ リンツ不正アクセス:314万件の対象確認とパスワード強制リセット後の対処
  3. 2026.10.27 テックブログ Joshin(ジョーシン)不正アクセス:最大600万件の対象とサブスク初期パスワードの対処
  4. 2026.10.29 テックブログ So-net情報漏洩:258万件の対象確認とメールのパスワード再設定、窓口の番号
  5. 2026.10.23 テックブログ Renta!はいつ再開?不正アクセスでメールアドレス等が流出したおそれ・再開時のパスワード再設定と運営側の対応

RELATED POSTS 関連記事

目次