ストーリー
高橋アーキテクトが複雑な画面のモックアップを見せた。 ダッシュボード画面には、ユーザー情報、最近のタスク、プロジェクト統計、通知が表示されている。
GraphQLとは
RESTの課題
// ダッシュボード画面に必要なデータを取得する場合
// REST: 複数のエンドポイントにリクエスト
const user = await fetch('/api/v1/users/me');
const tasks = await fetch('/api/v1/users/me/tasks?limit=5');
const projects = await fetch('/api/v1/projects?limit=3');
const notifications = await fetch('/api/v1/notifications?unread=true');
// 問題1: Over-fetching(不要なデータも取得する)
// ユーザー情報のうち、名前とアバターだけ欲しいのに全フィールドが返る
// 問題2: Under-fetching(データが足りず追加リクエストが必要)
// タスクの担当者名を表示するために、各タスクのユーザー情報も取得が必要
GraphQLのアプローチ
# GraphQL: 1回のリクエストで必要なデータだけ取得
query DashboardData {
me {
name
avatarUrl
}
myTasks(limit: 5) {
id
title
status
assignee {
name
}
}
projects(limit: 3) {
id
name
taskCount
}
notifications(unread: true) {
id
message
createdAt
}
}
GraphQLの3つの操作
1. Query(データ取得)
# Query: データの読み取り(RESTのGETに相当)
# ユーザー情報を取得
query GetUser {
user(id: "usr_123") {
id
name
email
tasks {
id
title
status
}
}
}
# レスポンス
{
"data": {
"user": {
"id": "usr_123",
"name": "田中太郎",
"email": "tanaka@example.com",
"tasks": [
{ "id": "tsk_1", "title": "API設計", "status": "IN_PROGRESS" },
{ "id": "tsk_2", "title": "テスト作成", "status": "TODO" }
]
}
}
}
2. Mutation(データ変更)
# Mutation: データの作成・更新・削除(RESTのPOST/PUT/DELETEに相当)
# タスクを作成
mutation CreateTask {
createTask(input: {
projectId: "proj_123"
title: "ログイン画面の改善"
priority: HIGH
assigneeId: "usr_456"
}) {
id
title
status
priority
assignee {
name
}
}
}
# レスポンス
{
"data": {
"createTask": {
"id": "tsk_789",
"title": "ログイン画面の改善",
"status": "TODO",
"priority": "HIGH",
"assignee": {
"name": "鈴木花子"
}
}
}
}
3. Subscription(リアルタイム通知)
# Subscription: リアルタイムのデータ更新通知(WebSocket)
# タスクのステータス変更を購読
subscription OnTaskUpdated {
taskUpdated(projectId: "proj_123") {
id
title
status
updatedBy {
name
}
}
}
# タスクが更新されるたびにデータが配信される
{
"data": {
"taskUpdated": {
"id": "tsk_789",
"title": "ログイン画面の改善",
"status": "IN_PROGRESS",
"updatedBy": { "name": "鈴木花子" }
}
}
}
TypeScriptでの実装
サーバー側(Apollo Server)
import { ApolloServer } from '@apollo/server';
import { startStandaloneServer } from '@apollo/server/standalone';
// 型定義(スキーマ)
const typeDefs = `#graphql
type User {
id: ID!
name: String!
email: String!
tasks: [Task!]!
}
type Task {
id: ID!
title: String!
description: String
status: TaskStatus!
priority: Priority!
assignee: User
createdAt: String!
}
enum TaskStatus {
TODO
IN_PROGRESS
IN_REVIEW
DONE
}
enum Priority {
LOW
MEDIUM
HIGH
CRITICAL
}
type Query {
user(id: ID!): User
tasks(projectId: ID!, limit: Int): [Task!]!
}
input CreateTaskInput {
projectId: ID!
title: String!
description: String
priority: Priority
assigneeId: ID
}
type Mutation {
createTask(input: CreateTaskInput!): Task!
updateTaskStatus(id: ID!, status: TaskStatus!): Task!
}
`;
// リゾルバー(各フィールドのデータ取得ロジック)
const resolvers = {
Query: {
user: async (_: unknown, { id }: { id: string }) => {
return await userRepository.findById(id);
},
tasks: async (_: unknown, { projectId, limit }: { projectId: string; limit?: number }) => {
return await taskRepository.findByProject(projectId, limit);
},
},
Mutation: {
createTask: async (_: unknown, { input }: { input: CreateTaskInput }) => {
return await taskService.create(input);
},
},
User: {
tasks: async (parent: User) => {
return await taskRepository.findByUserId(parent.id);
},
},
};
const server = new ApolloServer({ typeDefs, resolvers });
const { url } = await startStandaloneServer(server, { listen: { port: 4000 } });
クライアント側
// GraphQLクエリの実行
async function fetchDashboard(): Promise<DashboardData> {
const response = await fetch('http://localhost:4000/graphql', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`,
},
body: JSON.stringify({
query: `
query Dashboard {
user(id: "usr_123") {
name
email
}
tasks(projectId: "proj_123", limit: 5) {
id
title
status
}
}
`,
}),
});
const result = await response.json();
return result.data;
}
GraphQLの型システム
スカラー型
# 組み込みスカラー型
type Example {
id: ID! # 一意の識別子
name: String! # 文字列(! は非null必須)
age: Int # 整数(! がないのでnull許容)
price: Float # 浮動小数点
isActive: Boolean! # 真偽値
}
# カスタムスカラー型
scalar DateTime # 日付・時刻
scalar JSON # 任意のJSON
修飾子
type Task {
title: String! # 非null(必ず値がある)
description: String # null許容(値がない場合がある)
tags: [String!]! # 非nullの文字列配列(配列自体も非null)
labels: [Label] # null許容のLabel配列(配列自体もnull許容)
}
まとめ
| ポイント | 内容 |
|---|---|
| GraphQL | クライアントが必要なデータだけを指定して取得できるクエリ言語 |
| Query | データの読み取り(RESTのGET相当) |
| Mutation | データの変更(RESTのPOST/PUT/DELETE相当) |
| Subscription | リアルタイム通知(WebSocket) |
| 型システム | スカラー型、オブジェクト型、列挙型、Input型 |
| メリット | Over-fetching/Under-fetchingの解決、1リクエストで複数データ取得 |
チェックリスト
- GraphQLの3つの操作(Query, Mutation, Subscription)を理解した
- RESTの課題(Over-fetching, Under-fetching)を説明できる
- GraphQLの型システム(スカラー型、修飾子)を理解した
- サーバー側(Apollo Server)の基本構造を把握した
次のステップへ
GraphQLの基本概念を学びました。
次のセクションでは、スキーマ設計とリゾルバーの詳細を学びます。 効率的なスキーマ設計が、GraphQL APIの品質を決定します。
推定読了時間: 40分