Webアプリやモバイルアプリを開発していて、こんな悩みはありませんか?
「必要なデータだけ取得したいのに、APIが返すデータが多すぎる…」
「複数のAPIを何度も呼び出さないと、欲しい情報が揃わない…」
「APIの仕様変更のたびに、アプリ側も修正が必要で大変…」
そんな問題を解決するのがGraphQL(グラフキューエル)です!
FacebookがRESTful APIの課題を解決するために開発し、今ではGitHub、Shopify、Netflixなど、多くの企業が採用している技術なんです。
今回は、GraphQLの基本から、実践的な使い方、メリット・デメリット、RESTとの違いまで、初心者の方にも分かりやすく徹底解説していきますね!
GraphQLとは何か

まずは基本から理解しましょう。
GraphQLの定義
GraphQLは、APIのためのクエリ言語であり、実行環境です。
もっと簡単に言うと:
クライアント(アプリ)が、必要なデータだけを指定して取得できる仕組み
2012年にFacebook(現Meta)が開発し、2015年にオープンソース化されました。
名前の由来
Graph(グラフ) + QL(Query Language)
ここでいう「グラフ」は、データ同士の関係性をグラフ構造(ノードとエッジ)で表現することを指しています。
統計グラフとは関係ありません!
GraphQLの核心的なアイデア
「クライアントが欲しいデータの形を指定できる」
これがGraphQLの最大の特徴です。
従来のREST APIでは:
GET /user/123
→ サーバーが決めた形式でデータが返ってくる
GraphQLでは:
query {
user(id: 123) {
name
email
}
}
→ 指定した name と email だけが返ってくる
必要なものだけ、必要な形で取得できるんです。
身近な例で理解しよう
レストランでの注文を想像してみてください。
REST API(定食スタイル):
「Aセットください」と注文すると、決まったメニュー(ご飯、味噌汁、メイン、漬物)がすべて運ばれてきます。
「ご飯だけ欲しい」と思っても、セット全部が来てしまいます。
GraphQL(アラカルトスタイル):
「ご飯とメインだけください」と注文すると、指定したものだけが運ばれてきます。
必要なものだけを自由に組み合わせて注文できるんです!
これがGraphQLのイメージです。
RESTful APIとの違い
比較しながら理解を深めましょう。
データ取得の違い
RESTの場合:
ユーザー情報と、そのユーザーの投稿一覧を取得したい場合:
# 1回目のリクエスト
GET /users/123
→ ユーザー情報が返る
# 2回目のリクエスト
GET /users/123/posts
→ 投稿一覧が返る
2回のリクエストが必要です。
GraphQLの場合:
query {
user(id: 123) {
name
email
posts {
title
createdAt
}
}
}
1回のリクエストで全部取得できます!
Over-fetchingとUnder-fetching
Over-fetching(データの取りすぎ):
REST APIでは、サーバーが決めた形式でデータが返ってきます。
例えば、ユーザー名だけ欲しいのに:
{
"id": 123,
"name": "山田太郎",
"email": "yamada@example.com",
"age": 30,
"address": "東京都...",
"phone": "090-xxxx-xxxx",
"created_at": "2020-01-01",
"updated_at": "2025-01-01",
...
}
不要なデータまで全部返ってきます。
GraphQLなら:
query {
user(id: 123) {
name
}
}
{
"data": {
"user": {
"name": "山田太郎"
}
}
}
必要なフィールドだけ取得できます。
Under-fetching(データの取得不足):
RESTでは、必要な情報を得るために複数のエンドポイントを呼ぶ必要があります。
GraphQLなら、1回のクエリで関連データをまとめて取得できます。
エンドポイントの数
REST:
GET /users # ユーザー一覧
GET /users/:id # ユーザー詳細
POST /users # ユーザー作成
PUT /users/:id # ユーザー更新
DELETE /users/:id # ユーザー削除
GET /posts # 投稿一覧
GET /posts/:id # 投稿詳細
...
リソースごとに複数のエンドポイントが必要です。
GraphQL:
POST /graphql
単一のエンドポイントだけ!
すべての操作がこの1つのエンドポイントで完結します。
バージョニング
REST:
APIに変更が入ると、バージョン管理が必要:
GET /v1/users/:id
GET /v2/users/:id
GraphQL:
スキーマに新しいフィールドを追加するだけ。
古いクエリは引き続き動作します。
# 古いクエリ(そのまま動く)
query {
user(id: 123) {
name
}
}
# 新しいクエリ(新フィールドを使える)
query {
user(id: 123) {
name
newField # 後から追加されたフィールド
}
}
後方互換性が保ちやすいのが特徴です。
GraphQLの基本構成要素
GraphQLの主要な概念を理解しましょう。
1. Query(クエリ)- データの読み取り
Queryは、データを取得する操作です。REST APIのGETに相当します。
基本的な書き方:
query {
user(id: 123) {
name
email
}
}
実行結果:
{
"data": {
"user": {
"name": "山田太郎",
"email": "yamada@example.com"
}
}
}
複数のクエリを同時実行:
query {
user(id: 123) {
name
}
posts {
title
}
}
1回のリクエストで、ユーザー情報と投稿一覧の両方を取得できます!
2. Mutation(ミューテーション)- データの変更
Mutationは、データを作成・更新・削除する操作です。REST APIのPOST、PUT、DELETEに相当します。
ユーザー作成の例:
mutation {
createUser(input: {
name: "佐藤花子"
email: "sato@example.com"
}) {
id
name
email
}
}
実行結果:
{
"data": {
"createUser": {
"id": 124,
"name": "佐藤花子",
"email": "sato@example.com"
}
}
}
更新の例:
mutation {
updateUser(id: 123, input: {
name: "山田次郎"
}) {
id
name
}
}
削除の例:
mutation {
deleteUser(id: 123) {
success
message
}
}
3. Subscription(サブスクリプション)- リアルタイム更新
Subscriptionは、データの変更をリアルタイムで受け取る仕組みです。
WebSocketを使って、サーバーからクライアントへデータをプッシュします。
チャットメッセージの例:
subscription {
messageAdded(roomId: "room123") {
id
content
sender {
name
}
createdAt
}
}
新しいメッセージが投稿されるたびに、自動的にクライアントに通知されます。
使用例:
- チャットアプリ
- 通知システム
- リアルタイムダッシュボード
- 株価やスポーツスコアの更新
4. Schema(スキーマ)- APIの設計図
Schemaは、GraphQL APIの設計図です。
「どんなデータがあって、どんな操作ができるか」を定義します。
基本的なスキーマの例:
type User {
id: ID!
name: String!
email: String!
age: Int
posts: [Post!]!
}
type Post {
id: ID!
title: String!
content: String!
author: User!
createdAt: String!
}
type Query {
user(id: ID!): User
users: [User!]!
post(id: ID!): Post
}
type Mutation {
createUser(name: String!, email: String!): User!
updateUser(id: ID!, name: String, email: String): User!
deleteUser(id: ID!): Boolean!
}
記号の意味:
!: 必須フィールド(nullではない)[User!]: Userの配列[User!]!: 配列自体も必須(空配列はOK)
5. Type(型システム)
GraphQLは強い型付けを持っています。
基本的なスカラー型:
Int: 整数Float: 浮動小数点数String: 文字列Boolean: 真偽値ID: 一意な識別子
カスタム型:
type Address {
street: String!
city: String!
country: String!
zipCode: String!
}
type User {
id: ID!
name: String!
address: Address
}
Enum型(列挙型):
enum Role {
ADMIN
USER
GUEST
}
type User {
id: ID!
name: String!
role: Role!
}
GraphQLクエリの書き方
実践的な書き方を学びましょう。
基本的なクエリ
シンプルなクエリ:
{
user(id: 123) {
name
email
}
}
queryキーワードは省略できます。
ネストしたクエリ
関連するデータを一度に取得できます。
{
user(id: 123) {
name
posts {
title
comments {
content
author {
name
}
}
}
}
}
ユーザー → 投稿 → コメント → コメント作成者まで、1回のクエリで取得!
引数の使い方
単一の引数:
{
user(id: 123) {
name
}
}
複数の引数:
{
posts(limit: 10, offset: 0, orderBy: "createdAt") {
title
createdAt
}
}
オブジェクト型の引数:
{
users(filter: {
age: 30
city: "東京"
}) {
name
age
}
}
エイリアス(別名)
同じフィールドに違う引数で複数回アクセスしたい場合。
{
recentPosts: posts(limit: 5, orderBy: "createdAt") {
title
}
popularPosts: posts(limit: 5, orderBy: "views") {
title
}
}
結果:
{
"data": {
"recentPosts": [...],
"popularPosts": [...]
}
}
フラグメント(再利用可能な部分)
同じフィールドセットを繰り返し使う場合。
fragment UserInfo on User {
id
name
email
}
query {
user(id: 123) {
...UserInfo
posts {
author {
...UserInfo
}
}
}
}
...UserInfoでフラグメントを展開します。
変数の使用
動的な値をクエリに渡す場合。
クエリ定義:
query GetUser($userId: ID!) {
user(id: $userId) {
name
email
}
}
変数:
{
"userId": "123"
}
変数を使うことで、クエリを再利用しやすくなります。
ディレクティブ
条件付きでフィールドを含めたり除外したりします。
@include(条件が真なら含める):
query GetUser($withEmail: Boolean!) {
user(id: 123) {
name
email @include(if: $withEmail)
}
}
@skip(条件が真ならスキップ):
query GetUser($skipEmail: Boolean!) {
user(id: 123) {
name
email @skip(if: $skipEmail)
}
}
実践的な使用例

実際の開発シナリオで見てみましょう。
例1: ブログシステム
記事一覧ページ:
query {
posts(limit: 10) {
id
title
excerpt
createdAt
author {
name
avatar
}
commentCount
likeCount
}
}
1回のリクエストで、記事情報、著者情報、統計情報をすべて取得!
記事詳細ページ:
query {
post(id: 456) {
title
content
createdAt
author {
name
bio
avatar
}
comments(limit: 20) {
content
createdAt
author {
name
}
}
tags {
name
}
}
}
例2: ECサイト
商品詳細:
query {
product(id: 789) {
name
price
description
images {
url
alt
}
stock
category {
name
}
reviews(limit: 5) {
rating
comment
reviewer {
name
}
}
relatedProducts {
name
price
thumbnail
}
}
}
カート操作:
mutation {
addToCart(productId: 789, quantity: 1) {
cart {
items {
product {
name
price
}
quantity
}
totalPrice
}
}
}
例3: SNS
タイムライン:
query {
timeline(limit: 20) {
id
content
createdAt
author {
name
avatar
}
images {
url
}
likeCount
commentCount
isLikedByMe
}
}
投稿作成:
mutation {
createPost(input: {
content: "今日はいい天気!"
images: ["image1.jpg", "image2.jpg"]
}) {
id
content
createdAt
}
}
リアルタイム通知:
subscription {
notificationAdded {
id
type
content
sender {
name
avatar
}
createdAt
}
}
例4: ダッシュボード
統計データ:
query {
dashboard {
userCount
activeUserCount
todaySignups
revenue {
today
thisWeek
thisMonth
}
topProducts(limit: 5) {
name
sales
}
recentOrders(limit: 10) {
id
customer {
name
}
total
status
}
}
}
複数のデータソースから必要な情報をまとめて取得できます!
GraphQLのメリット
なぜGraphQLが人気なのか、利点を見てみましょう。
メリット1: 柔軟なデータ取得
クライアントが必要なデータだけを指定できます。
Over-fetchingの解消:
不要なデータを取得しなくて済むので、通信量が削減されます。
モバイルアプリなど、通信コストが重要な場面で特に有効です。
メリット2: 1回のリクエストで完結
複数のリソースを1回のリクエストで取得できます。
Under-fetchingの解消:
REST APIでは3回のリクエストが必要だった場面が、GraphQLなら1回で済みます。
レイテンシ(遅延)の削減につながります。
メリット3: 強い型付け
スキーマで型が定義されているため:
- 自動補完が効く
- エラーを早期に発見できる
- ドキュメント自動生成が可能
開発効率が大幅に向上します!
メリット4: 自己文書化
GraphQLはイントロスペクションという機能を持っています。
APIスキーマをクエリで取得できるため:
- GraphiQLやGraphQL Playgroundなどのツールで、インタラクティブにAPIを探索できる
- ドキュメントを別途書く必要がない
メリット5: バージョニング不要
新しいフィールドを追加しても、既存のクエリに影響しません。
非推奨フィールドを@deprecatedで示せます:
type User {
id: ID!
name: String!
fullName: String! @deprecated(reason: "Use 'name' instead")
}
メリット6: フロントエンドとバックエンドの独立
スキーマさえ決まれば:
- フロントエンド開発者は、スキーマに基づいてクエリを書ける
- バックエンド開発者は、スキーマに基づいて実装できる
並行開発が容易になります。
GraphQLのデメリット
良い点ばかりではありません。課題も理解しましょう。
デメリット1: 学習コストが高い
RESTより概念が複雑です。
- スキーマ定義言語(SDL)を学ぶ必要がある
- リゾルバー(Resolver)の実装が必要
- フラグメント、ディレクティブなど、新しい概念が多い
デメリット2: キャッシングが難しい
REST APIでは:
GET /users/123 → HTTPキャッシュが効く
GraphQLでは:
POST /graphql → 常に異なるクエリなので、標準的なHTTPキャッシュが効きにくい
解決策:
- Apollo Clientなどのライブラリを使う
- Persisted Queriesを使う
- DataLoaderでN+1問題を防ぐ
デメリット3: 複雑なクエリによるパフォーマンス問題
クライアントが自由にクエリを書けるため:
{
users {
posts {
comments {
author {
posts {
comments {
# 深くネストしすぎ...
}
}
}
}
}
}
}
サーバーに過度な負荷をかけるクエリを実行される可能性があります。
解決策:
- クエリの深さ制限
- クエリの複雑度制限
- タイムアウト設定
- レート制限
デメリット4: ファイルアップロードが標準でない
GraphQLの仕様には、ファイルアップロードが含まれていません。
解決策:
graphql-uploadなどのライブラリを使う- Base64エンコードする(小さいファイルのみ)
- 別のREST APIエンドポイントを用意
デメリット5: エラーハンドリングが複雑
HTTPステータスコードに頼れません。
GraphQLは、クエリが部分的に成功した場合でも200 OKを返します。
{
"data": {
"user": null
},
"errors": [
{
"message": "User not found",
"path": ["user"]
}
]
}
エラーはerrors配列で返されるため、独自のエラーハンドリング実装が必要です。
デメリット6: RESTful APIほど成熟していない
- ツールやライブラリがRESTほど豊富ではない
- ベストプラクティスがまだ確立途上
- 実装例が少ない場合がある
GraphQLの実装方法
実際にGraphQL APIを構築してみましょう。
サーバー側の実装(Node.js + Express)
必要なパッケージのインストール:
npm install express express-graphql graphql
基本的なサーバー:
const express = require('express');
const { graphqlHTTP } = require('express-graphql');
const { buildSchema } = require('graphql');
// スキーマ定義
const schema = buildSchema(`
type User {
id: ID!
name: String!
email: String!
}
type Query {
user(id: ID!): User
users: [User!]!
}
type Mutation {
createUser(name: String!, email: String!): User!
}
`);
// ダミーデータ
const users = [
{ id: '1', name: '山田太郎', email: 'yamada@example.com' },
{ id: '2', name: '佐藤花子', email: 'sato@example.com' }
];
// リゾルバー(実際のデータ取得・操作ロジック)
const root = {
user: ({ id }) => {
return users.find(user => user.id === id);
},
users: () => {
return users;
},
createUser: ({ name, email }) => {
const newUser = {
id: String(users.length + 1),
name,
email
};
users.push(newUser);
return newUser;
}
};
// サーバー起動
const app = express();
app.use('/graphql', graphqlHTTP({
schema: schema,
rootValue: root,
graphiql: true // GraphiQLインターフェースを有効化
}));
app.listen(4000, () => {
console.log('GraphQL server running at http://localhost:4000/graphql');
});
ブラウザでhttp://localhost:4000/graphqlにアクセスすると、GraphiQLが開きます!
クライアント側の実装(Vanilla JavaScript)
fetch APIを使う場合:
async function fetchUser(userId) {
const query = `
query {
user(id: "${userId}") {
id
name
email
}
}
`;
const response = await fetch('http://localhost:4000/graphql', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ query })
});
const result = await response.json();
return result.data.user;
}
// 使用例
fetchUser('1').then(user => {
console.log(user.name); // "山田太郎"
});
変数を使う場合:
async function createUser(name, email) {
const query = `
mutation CreateUser($name: String!, $email: String!) {
createUser(name: $name, email: $email) {
id
name
email
}
}
`;
const variables = { name, email };
const response = await fetch('http://localhost:4000/graphql', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ query, variables })
});
const result = await response.json();
return result.data.createUser;
}
// 使用例
createUser('田中一郎', 'tanaka@example.com');
Apollo Client(React)
より高機能なクライアントライブラリを使う場合。
インストール:
npm install @apollo/client graphql
セットアップ:
import { ApolloClient, InMemoryCache, ApolloProvider, useQuery, gql } from '@apollo/client';
// クライアント設定
const client = new ApolloClient({
uri: 'http://localhost:4000/graphql',
cache: new InMemoryCache()
});
// アプリケーション全体をラップ
function App() {
return (
<ApolloProvider client={client}>
<UserList />
</ApolloProvider>
);
}
// コンポーネントでクエリを使用
const GET_USERS = gql`
query GetUsers {
users {
id
name
email
}
}
`;
function UserList() {
const { loading, error, data } = useQuery(GET_USERS);
if (loading) return <p>Loading...</p>;
if (error) return <p>Error: {error.message}</p>;
return (
<ul>
{data.users.map(user => (
<li key={user.id}>{user.name} - {user.email}</li>
))}
</ul>
);
}
Apollo Clientは:
- 自動キャッシング
- ローディング状態の管理
- エラーハンドリング
- 楽観的UI更新
などの機能を提供してくれます!
便利なツールとライブラリ
GraphQL開発を助けるツールを紹介します。
GraphiQL
ブラウザベースのGraphQL IDEです。
主な機能:
- クエリの実行
- 自動補完
- スキーマの閲覧
- クエリ履歴
ほとんどのGraphQLサーバーライブラリで標準サポートされています。
GraphQL Playground
GraphiQLの後継的なツール。より高機能です。
- タブ機能
- HTTPヘッダーの設定
- 変数の管理
- スキーマの可視化
Postman
REST APIだけでなく、GraphQLもサポートしています。
- クエリのコレクション管理
- 環境変数
- テストスクリプト
Apollo Studio
Apollo提供の統合開発環境。
- スキーマレジストリ
- クエリのパフォーマンス監視
- スキーマチェック
- チーム開発機能
Hasura
データベースから自動的にGraphQL APIを生成してくれるツール。
PostgreSQL、MySQL、SQL Serverに対応。
コードを書かずにGraphQL APIを作れます!
Prisma
Node.jsのORMツールで、GraphQLとの統合が簡単。
TypeScriptの型安全性を活かせます。
トラブルシューティング
よくある問題と解決方法です。
問題1: N+1クエリ問題
症状:
ユーザー一覧を取得すると、データベースへのクエリが大量に発行される。
{
users {
name
posts { # 各ユーザーごとにクエリが発行される
title
}
}
}
解決方法: DataLoaderを使う
const DataLoader = require('dataloader');
const postLoader = new DataLoader(async (userIds) => {
// まとめてクエリを発行
const posts = await db.posts.findMany({
where: { userId: { in: userIds } }
});
// ユーザーIDごとにグループ化して返す
return userIds.map(id =>
posts.filter(post => post.userId === id)
);
});
// リゾルバーで使用
const resolvers = {
User: {
posts: (user) => postLoader.load(user.id)
}
};
問題2: CORSエラー
症状:
Access to fetch at 'http://localhost:4000/graphql' from origin
'http://localhost:3000' has been blocked by CORS policy
解決方法:
const cors = require('cors');
app.use(cors());
app.use('/graphql', graphqlHTTP({
// ...
}));
問題3: 認証・認可
症状:
ログインしたユーザーだけがアクセスできるようにしたい。
解決方法: Contextを使う
app.use('/graphql', graphqlHTTP({
schema: schema,
rootValue: root,
context: ({ req }) => {
// トークンからユーザー情報を取得
const token = req.headers.authorization || '';
const user = getUserFromToken(token);
return { user };
}
}));
// リゾルバーでチェック
const resolvers = {
Query: {
myProfile: (parent, args, context) => {
if (!context.user) {
throw new Error('認証が必要です');
}
return context.user;
}
}
};
問題4: エラーが分かりにくい
症状:
エラーメッセージが不親切。
解決方法: カスタムエラーを作る
class AuthenticationError extends Error {
constructor(message) {
super(message);
this.extensions = {
code: 'UNAUTHENTICATED'
};
}
}
class ValidationError extends Error {
constructor(message, fields) {
super(message);
this.extensions = {
code: 'BAD_USER_INPUT',
invalidFields: fields
};
}
}
// 使用例
if (!user) {
throw new AuthenticationError('ログインが必要です');
}
問題5: クエリが複雑すぎて遅い
症状:
深くネストしたクエリでサーバーが遅い。
解決方法: 深さ制限を設定
const depthLimit = require('graphql-depth-limit');
app.use('/graphql', graphqlHTTP({
schema: schema,
validationRules: [depthLimit(5)] // 最大5階層まで
}));
よくある質問と回答
Q1: GraphQLはRESTの完全な置き換え?
A: いいえ、必ずしもそうではありません。
GraphQLが向いている場合:
- 複雑なデータ関係
- 頻繁に変わるUI要件
- モバイルアプリ(通信量削減)
RESTが向いている場合:
- シンプルなCRUD操作
- ファイルアップロード/ダウンロード
- キャッシュが重要
両方を併用するハイブリッド構成も一般的です。
Q2: GraphQLはデータベースの技術?
A: いいえ、GraphQLはAPI層の技術です。
どんなデータソースとも組み合わせられます:
- SQL(PostgreSQL、MySQL)
- NoSQL(MongoDB、DynamoDB)
- REST API
- 他のGraphQL API
Q3: GraphQLは遅い?
A: 実装次第です。
適切に実装すれば、RESTより高速になることもあります:
- N+1問題を避ける(DataLoader使用)
- 適切なキャッシュ戦略
- クエリの複雑度制限
Q4: 既存のREST APIをGraphQLに移行できる?
A: はい、段階的に移行できます。
方法:
- GraphQLラッパーを作る(既存REST APIを内部で呼ぶ)
- 徐々に直接実装に置き換える
- 最終的にREST APIを削除
REST APIとGraphQL APIを並行稼働させることも可能です。
Q5: GraphQLは学ぶ価値がある?
A: はい、特にモダンなWeb開発では重要です。
理由:
- GitHub、Shopify、Facebookなど、大手企業が採用
- React、Vue、Angularとの相性が良い
- JAMstackやサーバーレスとの親和性が高い
- 将来性のある技術
Q6: GraphQLのセキュリティ対策は?
A: 以下の対策が推奨されます:
- クエリの深さ制限
- クエリの複雑度制限
- レート制限
- 認証・認可の実装
- 入力値のバリデーション
- Persisted Queries(ホワイトリスト方式)
Q7: TypeScriptとの相性は?
A: 非常に良いです!
GraphQLのスキーマから、TypeScriptの型定義を自動生成できます。
# graphql-codegenを使う
npm install -D @graphql-codegen/cli
スキーマと実装の型が自動的に一致するため、型安全性が高まります。
Q8: Subscriptionは必須?
A: いいえ、必要な場合のみ実装すればOKです。
QueryとMutationだけでも十分に機能します。
リアルタイム機能が必要な場合のみ、Subscriptionを追加しましょう。
まとめ: GraphQLで効率的なAPI開発を
GraphQLについて解説してきました。最後にポイントをまとめます。
重要ポイント:
- GraphQLはクライアント主導でデータを取得できるAPI技術
- 1回のリクエストで複数のリソースを取得可能
- 強い型付けにより、開発効率とコード品質が向上
- RESTとは異なるアプローチで、それぞれに適した用途がある
- 学習コストはあるが、モダンなWeb開発では重要な技術
GraphQLは、REST APIの課題を解決する強力な技術です。
特に、複雑なデータ関係を扱うアプリケーションや、モバイルアプリで通信量を削減したい場合に威力を発揮します。
最初は難しく感じるかもしれませんが、基本を理解すれば、柔軟で効率的なAPI開発ができるようになりますよ。
まずは小さなプロジェクトから始めて、徐々に機能を追加していくのがおすすめです!
それでは、GraphQLで快適なAPI開発ライフを!

コメント