EXERCISE 90分

ストーリー

高橋アーキテクト
さあ、実際にAPIを設計してみよう

高橋アーキテクトが要件書を渡した。

高橋アーキテクト
タスク管理アプリのAPIを設計してほしい。エンドポイント、リクエスト・レスポンスの形式、認証方式、エラーハンドリング、すべてを自分で考えて設計するんだ
あなた
90分で全部ですか?
高橋アーキテクト
段階的にやろう。6つのミッションに分けてある。分からなかったらヒントを見ていい。でもまずは自分で考えてみてくれ

ミッション概要

タスク管理アプリ「TaskFlow」のREST APIを設計します。

ミッションテーマ難易度
Mission 1リソース設計初級
Mission 2エンドポイント一覧の作成初級
Mission 3リクエスト・レスポンス設計中級
Mission 4エラーレスポンス設計中級
Mission 5認証・認可の設計上級
Mission 6ページネーション・フィルタ設計上級

Mission 1: リソース設計(10分)

以下の要件を読み、APIのリソースを洗い出してください。

要件

  • ユーザーはアカウントを作成してログインできる
  • ユーザーはプロジェクトを作成できる
  • プロジェクトにはタスクを追加できる
  • タスクにはラベル(複数)を付けられる
  • タスクにはコメントを投稿できる
  • ユーザーをプロジェクトのメンバーとして招待できる

期待する成果

リソース一覧と、それぞれの関係(親子関係)をリストアップしてください。

解答
// リソース一覧
// 1. Users(ユーザー)
// 2. Projects(プロジェクト)
// 3. Tasks(タスク)-- Projects のサブリソース
// 4. Labels(ラベル)-- Projects のサブリソース
// 5. Comments(コメント)-- Tasks のサブリソース
// 6. Members(メンバー)-- Projects のサブリソース

// リソースの関係
// Users ──┬── Projects(ユーザーはプロジェクトを作成)
//          │     ├── Tasks(プロジェクトにタスク)
//          │     │     └── Comments(タスクにコメント)
//          │     ├── Labels(プロジェクトにラベル)
//          │     └── Members(プロジェクトにメンバー)
//          └── (ユーザー自身のプロフィール情報)

Mission 2: エンドポイント一覧の作成(15分)

Mission 1で洗い出したリソースに対して、CRUDのエンドポイントを設計してください。

要件

  • すべてのリソースに対してCRUD操作を定義
  • URLのパスは api/v1 で始める
  • RESTの規約に従う(複数形、名詞、HTTPメソッド)
解答
// === ユーザー ===
POST   /api/v1/users                          // ユーザー登録
GET    /api/v1/users/me                       // 自分の情報取得
PATCH  /api/v1/users/me                       // 自分の情報更新

// === 認証 ===
POST   /api/v1/auth/login                     // ログイン
POST   /api/v1/auth/logout                    // ログアウト
POST   /api/v1/auth/refresh                   // トークンリフレッシュ

// === プロジェクト ===
GET    /api/v1/projects                       // プロジェクト一覧
POST   /api/v1/projects                       // プロジェクト作成
GET    /api/v1/projects/:projectId            // プロジェクト詳細
PATCH  /api/v1/projects/:projectId            // プロジェクト更新
DELETE /api/v1/projects/:projectId            // プロジェクト削除

// === メンバー(プロジェクトのサブリソース)===
GET    /api/v1/projects/:projectId/members              // メンバー一覧
POST   /api/v1/projects/:projectId/members              // メンバー招待
DELETE /api/v1/projects/:projectId/members/:userId      // メンバー削除

// === タスク(プロジェクトのサブリソース)===
GET    /api/v1/projects/:projectId/tasks                // タスク一覧
POST   /api/v1/projects/:projectId/tasks                // タスク作成
GET    /api/v1/tasks/:taskId                            // タスク詳細
PATCH  /api/v1/tasks/:taskId                            // タスク更新
DELETE /api/v1/tasks/:taskId                            // タスク削除

// === ラベル(プロジェクトのサブリソース)===
GET    /api/v1/projects/:projectId/labels               // ラベル一覧
POST   /api/v1/projects/:projectId/labels               // ラベル作成
PATCH  /api/v1/labels/:labelId                          // ラベル更新
DELETE /api/v1/labels/:labelId                          // ラベル削除

// === コメント(タスクのサブリソース)===
GET    /api/v1/tasks/:taskId/comments                   // コメント一覧
POST   /api/v1/tasks/:taskId/comments                   // コメント投稿
PATCH  /api/v1/comments/:commentId                      // コメント編集
DELETE /api/v1/comments/:commentId                      // コメント削除

Mission 3: リクエスト・レスポンス設計(20分)

主要なエンドポイントのリクエスト・レスポンスのJSON形式を設計してください。

要件

以下の4つのエンドポイントの入出力を設計すること。

  1. タスク作成 POST /api/v1/projects/:projectId/tasks
  2. タスク一覧取得 GET /api/v1/projects/:projectId/tasks
  3. タスク更新 PATCH /api/v1/tasks/:taskId
  4. タスク詳細取得 GET /api/v1/tasks/:taskId
解答
// 1. タスク作成
// POST /api/v1/projects/proj_123/tasks
// Request:
{
  "title": "ログイン画面のUI改善",
  "description": "モバイル対応のレスポンシブデザインに修正",
  "assigneeId": "usr_456",
  "priority": "high",
  "dueDate": "2025-02-28T23:59:59Z",
  "labelIds": ["lbl_001", "lbl_003"]
}
// Response: 201 Created
// Location: /api/v1/tasks/tsk_789
{
  "data": {
    "id": "tsk_789",
    "title": "ログイン画面のUI改善",
    "description": "モバイル対応のレスポンシブデザインに修正",
    "status": "todo",
    "priority": "high",
    "assignee": {
      "id": "usr_456",
      "name": "鈴木花子"
    },
    "labels": [
      { "id": "lbl_001", "name": "UI", "color": "#3b82f6" },
      { "id": "lbl_003", "name": "改善", "color": "#10b981" }
    ],
    "dueDate": "2025-02-28T23:59:59Z",
    "createdAt": "2025-01-15T09:00:00Z",
    "updatedAt": "2025-01-15T09:00:00Z"
  }
}

// 2. タスク一覧取得
// GET /api/v1/projects/proj_123/tasks?status=todo&sort=-priority&page=1&perPage=20
// Response: 200 OK
{
  "data": [
    {
      "id": "tsk_789",
      "title": "ログイン画面のUI改善",
      "status": "todo",
      "priority": "high",
      "assignee": { "id": "usr_456", "name": "鈴木花子" },
      "dueDate": "2025-02-28T23:59:59Z",
      "createdAt": "2025-01-15T09:00:00Z"
    }
  ],
  "meta": {
    "totalCount": 42,
    "currentPage": 1,
    "perPage": 20,
    "totalPages": 3
  }
}

// 3. タスク更新
// PATCH /api/v1/tasks/tsk_789
// Request:
{
  "status": "in_progress",
  "priority": "medium"
}
// Response: 200 OK
{
  "data": {
    "id": "tsk_789",
    "title": "ログイン画面のUI改善",
    "status": "in_progress",
    "priority": "medium",
    "updatedAt": "2025-01-16T10:30:00Z"
  }
}

// 4. タスク詳細取得
// GET /api/v1/tasks/tsk_789
// Response: 200 OK
{
  "data": {
    "id": "tsk_789",
    "title": "ログイン画面のUI改善",
    "description": "モバイル対応のレスポンシブデザインに修正",
    "status": "in_progress",
    "priority": "medium",
    "assignee": { "id": "usr_456", "name": "鈴木花子" },
    "labels": [
      { "id": "lbl_001", "name": "UI", "color": "#3b82f6" },
      { "id": "lbl_003", "name": "改善", "color": "#10b981" }
    ],
    "project": { "id": "proj_123", "name": "TaskFlow v2" },
    "commentCount": 5,
    "dueDate": "2025-02-28T23:59:59Z",
    "createdAt": "2025-01-15T09:00:00Z",
    "updatedAt": "2025-01-16T10:30:00Z"
  }
}

Mission 4: エラーレスポンス設計(15分)

以下のエラーケースに対するレスポンスを設計してください。

  1. 存在しないタスクへのアクセス
  2. バリデーションエラー(タイトルが空、優先度が不正)
  3. プロジェクトメンバーでないユーザーがタスクを作成しようとした
  4. レート制限超過
解答
// 1. 存在しないタスク: 404 Not Found
{
  "error": {
    "code": "NOT_FOUND",
    "message": "指定されたタスクが見つかりません",
    "traceId": "req_abc123"
  }
}

// 2. バリデーションエラー: 422 Unprocessable Entity
{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "入力内容に問題があります",
    "details": [
      { "field": "title", "message": "タイトルは必須です" },
      { "field": "priority", "message": "priority は low, medium, high のいずれかを指定してください", "value": "urgent" }
    ],
    "traceId": "req_def456"
  }
}

// 3. 権限エラー: 403 Forbidden
{
  "error": {
    "code": "FORBIDDEN",
    "message": "このプロジェクトのメンバーではないため、タスクを作成できません",
    "traceId": "req_ghi789"
  }
}

// 4. レート制限超過: 429 Too Many Requests
// Headers: Retry-After: 3600
{
  "error": {
    "code": "RATE_LIMITED",
    "message": "リクエスト数が制限を超えました。3600秒後に再試行してください。",
    "retryAfter": 3600,
    "traceId": "req_jkl012"
  }
}

Mission 5: 認証・認可の設計(15分)

TaskFlow APIの認証・認可の方式を設計してください。

要件

  • JWT ベースの認証
  • ロールは owner, admin, member, viewer の4種類
  • プロジェクトごとにロールが異なる
解答
// 認証フロー
// 1. ログイン → JWTトークン取得
POST /api/v1/auth/login
Request: { "email": "tanaka@example.com", "password": "..." }
Response: {
  "data": {
    "accessToken": "eyJhbG...",
    "refreshToken": "ref_abc...",
    "expiresIn": 900
  }
}

// 2. APIリクエストにトークンを付与
GET /api/v1/projects
Authorization: Bearer eyJhbG...

// ロール定義と権限マトリクス
// | 操作           | owner | admin | member | viewer |
// |---------------|-------|-------|--------|--------|
// | プロジェクト削除 | o     |       |        |        |
// | メンバー管理    | o     | o     |        |        |
// | タスク作成/更新  | o     | o     | o      |        |
// | タスク閲覧      | o     | o     | o      | o      |
// | コメント投稿    | o     | o     | o      |        |

// JWTペイロード
{
  "sub": "usr_123",
  "name": "田中太郎",
  "iat": 1705305600,
  "exp": 1705306500
}

// プロジェクトメンバーシップ(DBで管理)
interface ProjectMembership {
  projectId: string;
  userId: string;
  role: 'owner' | 'admin' | 'member' | 'viewer';
  joinedAt: string;
}

// 認可ミドルウェア
function requireProjectRole(...allowedRoles: string[]) {
  return async (req: Request, res: Response, next: NextFunction) => {
    const { projectId } = req.params;
    const userId = req.user.id;

    const membership = await db.projectMembers.findOne({ projectId, userId });
    if (!membership || !allowedRoles.includes(membership.role)) {
      return res.status(403).json({
        error: { code: 'FORBIDDEN', message: 'この操作の権限がありません' }
      });
    }
    next();
  };
}

// 使用例
app.post('/api/v1/projects/:projectId/tasks',
  authenticate,
  requireProjectRole('owner', 'admin', 'member'),
  createTask
);

Mission 6: ページネーション・フィルタ設計(15分)

タスク一覧APIのフィルタリング・ソート・ページネーションを設計してください。

解答
// タスク一覧のクエリパラメータ設計
GET /api/v1/projects/:projectId/tasks
  ?status=todo,in_progress          // ステータスで絞り込み(カンマ区切りで複数)
  &priority=high                    // 優先度で絞り込み
  &assigneeId=usr_456               // 担当者で絞り込み
  &labelId=lbl_001                  // ラベルで絞り込み
  &search=ログイン                   // タイトル・説明文の部分一致検索
  &dueBefore=2025-02-28             // 期限日の範囲(以前)
  &dueAfter=2025-01-01              // 期限日の範囲(以降)
  &sort=-priority,createdAt         // ソート(-で降順)
  &page=1                           // ページ番号
  &perPage=20                       // 1ページあたりの件数

// TypeScript の型定義
interface TaskListQuery {
  status?: string;         // カンマ区切り
  priority?: 'low' | 'medium' | 'high';
  assigneeId?: string;
  labelId?: string;
  search?: string;
  dueBefore?: string;      // ISO 8601 date
  dueAfter?: string;       // ISO 8601 date
  sort?: string;           // デフォルト: '-createdAt'
  page?: number;           // デフォルト: 1
  perPage?: number;        // デフォルト: 20, 最大: 100
}

// レスポンス
{
  "data": [...],
  "meta": {
    "totalCount": 42,
    "currentPage": 1,
    "perPage": 20,
    "totalPages": 3
  },
  "links": {
    "self": "/api/v1/projects/proj_123/tasks?page=1&perPage=20",
    "next": "/api/v1/projects/proj_123/tasks?page=2&perPage=20",
    "last": "/api/v1/projects/proj_123/tasks?page=3&perPage=20"
  }
}

達成度チェック

ミッションテーマ完了
Mission 1リソース設計[ ]
Mission 2エンドポイント一覧[ ]
Mission 3リクエスト・レスポンス設計[ ]
Mission 4エラーレスポンス設計[ ]
Mission 5認証・認可の設計[ ]
Mission 6ページネーション・フィルタ設計[ ]

まとめ

ポイント内容
リソース設計ユースケースからリソースを洗い出し、関係を整理する
エンドポイントRESTの規約に従い、一貫したURL構造を設計する
レスポンス形式data + meta の統一構造、プレフィックス付きID
エラー設計適切なステータスコード + 構造化エラー情報
認証・認可JWT + ロールベースのアクセス制御

チェックリスト

  • 要件からリソースを洗い出し、関係を整理できた
  • RESTful なエンドポイント一覧を設計できた
  • リクエスト・レスポンスのJSON構造を設計できた
  • エラーケースに対する適切なレスポンスを設計できた
  • 認証・認可の方式を選択し、権限マトリクスを定義できた

次のステップへ

お疲れさまでした。RESTful APIの設計を実践しました。

次はStep 2のチェックポイントです。 リソース設計、HATEOAS、認証、レート制限の知識を確認しましょう。


推定所要時間: 90分