404 Not Found

404 Not Found


nginx

REST APIの設計:コラボレーションへの道――混沌から明快さへ

アリスのチームは、タスク管理システムのフロントエンドとバックエンドの両方を開発しています。フロントエンド開発者たちは、どのAPIが利用可能か、どのHTTPメソッドを使用すべきか、またレスポンスがどのような形式になるのかが分からないと不満を漏らしています。バックエンド開発者たちも同様に不満を抱えています。同じ「タスクの更新」操作であっても、POSTを使う者もいれば、PUTを使う者、PATCHを使う者もおり、その結果、レスポンスの形式がまちまちになっています。チーム間の連携は混乱状態に陥っています。

アリスはREST仕様を採用することに決めました。チームがリソースの命名規則、メソッドのマッピング、ステータスコード、およびレスポンス形式を標準化した結果、APIは明確で予測可能なものになりました。これにより、フロントエンドチームはAPIドキュメントを繰り返し確認する必要がなくなり、連携の効率は2倍になりました。

1. 学習内容


2. RESTアーキテクチャの原則

(1) RESTとは何ですか?

REST(Representational State Transfer)は、2000年にロイ・フィールディングによって提唱されたソフトウェアアーキテクチャのスタイルである。これは、Webアプリケーションのインターフェースを設計するための一連の制約を定義している。RESTはプロトコルや標準ではなく、あくまで設計哲学である。

(2) 4つの基本原則

原則 意味
リソース すべてはリソースであり、URLによって識別される /tasks, /users/42
表現層 リソースが表現される形式(JSONなど) {"id": 1, "title": "Learn REST"}
ステートレス 各リクエストには必要な情報がすべて含まれている リクエストにはトークンが含まれており、セッションに依存しない
一元化されたインターフェース 標準的なHTTPメソッドを使用してリソースを操作 GETで読み取り、POSTで作成、DELETEで削除

▶ 例:ステートレスとステートフル

JAVASCRIPT
// Stateful: Depends on server session (not RESTful)
app.post('/login', (req, res) => {
  req.session.userId = 42; // Server State Saving
  res.send('logged in');
});

app.get('/profile', (req, res) => {
  const userId = req.session.userId; // Depends on the server status
  res.json({ id: userId, name: 'Alice' });
});

// Stateless:Include authentication information with every request(RESTful)
app.get('/profile', (req, res) => {
  const userId = verifyToken(req.headers.authorization);
  res.json({ id: userId, name: 'Alice' });
});

3. CRUD と HTTP メソッドのマッピング

(1) 標準的なマッピング関係

RESTの核心となる考え方は、URLに行動を表す動詞を埋め込むのではなく、HTTPメソッドを用いてリソースに対する操作の意図を表現することにある。

CRUD操作 HTTPメソッド パス 冪等性 セキュリティ
作成 投稿 /tasks いいえ いいえ
読み取り(一覧) GET /tasks はい はい
閲覧(単体) GET /tasks/42 はい はい
更新(全文) PUT /tasks/42 はい いいえ
更新(一部) パッチ /tasks/42 いいえ いいえ
削除 削除 /tasks/42 はい いいえ

(2) 冪等性に関する詳細な説明

冪等性とは、同じリクエストを1回実行しても、複数回実行しても同じ結果が得られることを指します。GET、PUT、DELETEは冪等ですが、POSTやPATCHは冪等ではありません。

▶ 例:PUTとPOSTの冪等性の違い

JAVASCRIPT
// POST: Creates a new resource on every call (Non-idempotent)
// 1st POST /tasks → Create id=1
// 2nd POST /tasks → Create id=2
app.post('/tasks', (req, res) => {
  const task = { id: nextId++, ...req.body };
  tasks.push(task);
  res.status(201).json(task);
});

// PUT: Replaces the same resource on every call (Idempotent)
// 1st PUT /tasks/1 → Replace id=1
// 2nd PUT /tasks/1 → Replace id=1 (The results are the same)
app.put('/tasks/:id', (req, res) => {
  const idx = tasks.findIndex(t => t.id === parseInt(req.params.id));
  if (idx === -1) return res.status(404).json({ error: 'Not found' });
  tasks[idx] = { id: parseInt(req.params.id), ...req.body };
  res.json(tasks[idx]);
});

▶ 例:PATCH による部分更新

JAVASCRIPT
// PATCH:Modify only the fields provided
app.patch('/tasks/:id', (req, res) => {
  const task = tasks.find(t => t.id === parseInt(req.params.id));
  if (!task) return res.status(404).json({ error: 'Not found' });
  Object.assign(task, req.body);
  res.json(task);
});

// Request:Modify only status Field
// PATCH /tasks/1  {"status": "done"}
// Raw Data:{"id":1,"title":"Learn REST","status":"pending"}
// Results:{"id":1,"title":"Learn REST","status":"done"}

4. URLの設計ガイドライン

(1) 基本ルール

RESTfulなURL設計では、APIを直感的で読みやすいものにするための一連の規約に従います。

ルール 正解 ✅ 誤り ❌
動詞ではなく名詞を使う GET /tasks GET /getTasks
単数形ではなく複数形を使う /tasks /task
ネスト構造による関係の表現 /users/42/tasks /tasksByUser?userId=42
最大3レベル /users/42/tasks/1 /orgs/1/teams/2/users/42/tasks
クエリパラメータで絞り込む /tasks?status=done /doneTasks
kebab-case の使用 /task-items /taskItems

(2) ネストされたリソースの設計

ネストされたリソースは、階層的な関係を示します。子リソースが親リソースから独立して存在できない場合は、ネストされたパスを使用してください。

▶ 例:タスク管理システムのURL設計

TEXT
# Mission Resources
GET    /tasks              # Get the task list
POST   /tasks              # Create a New Task
GET    /tasks/42           # Get a Single Task
PUT    /tasks/42           # Full Update Task
PATCH  /tasks/42           # Partial Update Task
DELETE /tasks/42           # Delete Task

# Comments on the Assignment(Nested Resources)
GET    /tasks/42/comments           # Get a Task42List of comments
POST   /tasks/42/comments           # For the mission42Add a comment
GET    /tasks/42/comments/7         # Get a Task42Comments on7
DELETE /tasks/42/comments/7         # Delete Comment7

# Filtering and Pagination
GET    /tasks?status=done&page=2&limit=20
GET    /tasks?sort=-created_at      # Sort by creation date in reverse chronological order

▶ 例:URLクエリパラメータの一般的な使い方

JAVASCRIPT
app.get('/tasks', (req, res) => {
  let result = [...tasks];

  // Filter
  if (req.query.status) {
    result = result.filter(t => t.status === req.query.status);
  }

  // Sort
  if (req.query.sort) {
    const field = req.query.sort.startsWith('-')
      ? req.query.sort.slice(1)
      : req.query.sort;
    const order = req.query.sort.startsWith('-') ? -1 : 1;
    result.sort((a, b) => (a[field] > b[field] ? order : -order));
  }

  // Pagination
  const page = parseInt(req.query.page) || 1;
  const limit = parseInt(req.query.limit) || 20;
  const start = (page - 1) * limit;
  result = result.slice(start, start + limit);

  res.json({
    data: result,
    page,
    limit,
    total: tasks.length
  });
});

5. HTTPステータスコードの選択

(1) ステータスコードの分類と選定

HTTPステータスコードは、REST APIとクライアント間の通信における重要なシグナルです。適切なステータスコードを選択することで、クライアントはリクエストの結果を正確に把握することができます。

シナリオ ステータスコード 意味 説明
リソースの取得に成功しました 200 OK リクエストが成功しました GET/PUT/PATCH が成功した際に返されるステータス
リソースの作成に成功しました 201 作成済み リソースが作成されました POSTが成功した際に返されるステータスコード。Locationヘッダーが含まれている必要があります
リソースの削除に成功しました 204 コンテンツなし コンテンツなし DELETE が成功した際に返されるステータスコード。レスポンス本文はありません
無効なリクエストパラメータ 400 Bad Request クライアントリクエストの構文エラー 必須フィールドの欠落、形式が不正
認証されていません 401 認証なし 認証情報が提供されていません トークンがありません、または無効です
権限なし 403 アクセス拒否 認証済みだが権限なし 一般ユーザーが管理画面にアクセス
リソースが存在しません 404 Not Found 要求されたリソースは存在しません この ID のリソースが見つかりませんでした
サーバーエラー 500 内部サーバーエラー 内部サーバーエラー 未処理の例外

(2) よくある間違い:ステータスコードの誤用

▶ 例:ステータスコードの正しい使い方

JAVASCRIPT
// Create a Resource → 201 + Location
app.post('/tasks', (req, res) => {
  const task = { id: nextId++, ...req.body };
  tasks.push(task);
  res.status(201)
     .location(`/tasks/${task.id}`)
     .json(task);
});

// Delete Resource → 204(Non-responsive body)
app.delete('/tasks/:id', (req, res) => {
  const idx = tasks.findIndex(t => t.id === parseInt(req.params.id));
  if (idx === -1) return res.status(404).json({ error: 'Not found' });
  tasks.splice(idx, 1);
  res.status(204).end();
});

// Verification Failed → 400 + Error Details
app.post('/tasks', (req, res) => {
  if (!req.body.title) {
    return res.status(400).json({
      error: 'Validation failed',
      details: [{ field: 'title', message: 'Title is required' }]
    });
  }
  // ...
});

6. リクエストとレスポンスの形式

(1) JSON仕様の規約

規約 標準
フィールド名 camelCase createdAt, taskId
日付形式 ISO 8601 2025-07-03T10:30:00Z
リストの応答 データとページネーション情報を含む {"data": [...], "total": 100}
エラー応答 エラーと詳細を含む {"error": "Not found", "details": [...]}
NULL値の扱い フィールドを省略する代わりに null を使用する {"description": null}
ID タイプ 文字列(精度の問題を避けるため) {"id": "42"}

▶ 例:標準化されたリスト形式の回答

JAVASCRIPT
app.get('/tasks', (req, res) => {
  const page = parseInt(req.query.page) || 1;
  const limit = parseInt(req.query.limit) || 20;
  const start = (page - 1) * limit;
  const data = tasks.slice(start, start + limit);

  res.json({
    data,
    pagination: {
      page,
      limit,
      total: tasks.length,
      totalPages: Math.ceil(tasks.length / limit)
    }
  });
});
TEXT
// Response Example
{
  "data": [
    {
      "id": "1",
      "title": "Learn REST",
      "status": "pending",
      "createdAt": "2025-07-03T10:30:00Z"
    }
  ],
  "pagination": {
    "page": 1,
    "limit": 20,
    "total": 1,
    "totalPages": 1
  }
}

▶ 例:標準化されたエラー応答

JAVASCRIPT
// Unified Error Handling Middleware
app.use((err, req, res, next) => {
  console.error(err.stack);
  res.status(err.status || 500).json({
    error: err.message || 'Internal Server Error',
    details: err.details || [],
    requestId: req.id,
    timestamp: new Date().toISOString()
  });
});

// Custom Error Classes
class ApiError extends Error {
  constructor(status, message, details = []) {
    super(message);
    this.status = status;
    this.details = details;
  }
}

// Usage
app.get('/tasks/:id', (req, res, next) => {
  const task = tasks.find(t => t.id === parseInt(req.params.id));
  if (!task) {
    return next(new ApiError(404, 'Task not found', [
      { field: 'id', message: `No task with id ${req.params.id}` }
    ]));
  }
  res.json(task);
});
TEXT
// Examples of Error Responses
{
  "error": "Task not found",
  "details": [
    { "field": "id", "message": "No task with id 999" }
  ],
  "requestId": "req-a1b2c3",
  "timestamp": "2025-07-03T10:30:00Z"
}

7. APIのバージョン管理戦略

(1) 3つの主流戦略の比較

戦略 メリット デメリット 適用可能なシナリオ
URLパス /api/v1/tasks 直感的で、ブラウザ上でテスト可能 URLが長くなるため、RESTの純粋主義者の間では賛否が分かれる ほとんどの公開API
リクエストヘッダー Accept: application/vnd.myapi.v1+json すっきりとした純粋なRESTful URL 直感的ではなく、デバッグが難しい RESTの純粋性を追求
クエリパラメータ /api/tasks?version=1 最も単純 見落としやすい;複雑なキャッシュ戦略 内部API、単純なプロジェクト

(2) バージョン管理のベストプラクティス

▶ 例:URLパスのバージョン管理の実装

JAVASCRIPT
// Routing Structure
// /api/v1/tasks → v1 Logic
// /api/v2/tasks → v2 Logic

const express = require('express');
const app = express();

// v1 Routing
const v1Router = express.Router();
v1Router.get('/tasks', (req, res) => {
  res.json({ data: tasks, version: 'v1' }); // v1 Return Format
});

// v2 Routing(Response Format Upgrade)
const v2Router = express.Router();
v2Router.get('/tasks', (req, res) => {
  res.json({                        // v2 Return Format(Includes pagination)
    data: tasks,
    pagination: { page: 1, total: tasks.length }
  });
});

app.use('/api/v1', v1Router);
app.use('/api/v2', v2Router);

// Response Header Version
app.use('/api/v2', (req, res, next) => {
  res.setHeader('X-API-Version', '2.0');
  next();
});

8. REST成熟度モデル

(1) リチャードソンの成熟度モデル

レナード・リチャードソンは、APIのRESTful成熟度を測定するためのモデルを提案した:

100%
graph TD
    L0["Level 0: Single endpoint<br/>HTTP as a tunnel<br/>e.g. POST /api  {action: getTasks}"]
    L1["Level 1: Resource URLs<br/>One URL per resource<br/>e.g. POST /tasks, POST /users"]
    L2["Level 2: HTTP Methods<br/>GET/POST/PUT/DELETE<br/>e.g. GET /tasks, DELETE /tasks/1"]
    L3["Level 3: HATEOAS<br/>Responses contain hyperlinks<br/>e.g. Response includes next, self links"]
    L0 --> L1 --> L2 --> L3
    style L0 fill:#ff6b6b,color:#fff
    style L1 fill:#ffa502,color:#fff
    style L2 fill:#2ed573,color:#fff
    style L3 fill:#1e90ff,color:#fff
レベル 特徴 サンプルリクエスト サンプルレスポンス
レベル 0 HTTP トンネル、単一 URL POST /api {"action":"getTasks"} {"tasks": [...]}
レベル 1 資源の分別;方法は問わない POST /tasks {"tasks": [...]}
レベル 2 意味的に正しい HTTP GET /tasks 200 {"data": [...]}
レベル 3 HATEOAS ハイパーメディア GET /tasks/1 _links ナビゲーションを含む

(2) HATEOASの詳細な解説

HATEOAS(Hypermedia as the Engine of Application State)では、レスポンスに関連する操作へのリンクを含めることが求められているため、クライアントはURLをハードコーディングする必要がありません。

▶ 例:レベル 3 — HATEOAS リンクを含む応答

JAVASCRIPT
app.get('/tasks/:id', (req, res) => {
  const task = tasks.find(t => t.id === parseInt(req.params.id));
  if (!task) return res.status(404).json({ error: 'Not found' });

  res.json({
    ...task,
    _links: {
      self: { href: `/tasks/${task.id}`, method: 'GET' },
      update: { href: `/tasks/${task.id}`, method: 'PUT' },
      delete: { href: `/tasks/${task.id}`, method: 'DELETE' },
      assign: { href: `/tasks/${task.id}/assignee`, method: 'POST' },
      comments: { href: `/tasks/${task.id}/comments`, method: 'GET' }
    }
  });
});
TEXT
// Response
{
  "id": "1",
  "title": "Learn REST",
  "status": "pending",
  "createdAt": "2025-07-03T10:30:00Z",
  "_links": {
    "self": { "href": "/tasks/1", "method": "GET" },
    "update": { "href": "/tasks/1", "method": "PUT" },
    "delete": { "href": "/tasks/1", "method": "DELETE" },
    "assign": { "href": "/tasks/1/assignee", "method": "POST" },
    "comments": { "href": "/tasks/1/comments", "method": "GET" }
  }
}

9. 包括的な例:タスク管理APIの完全な設計

アリスのチームは、タスク管理システム向けに、リソースの定義からエラー処理に至るまでを網羅した、完全なRESTful APIを設計しました。

▶ 例:タスク管理 API の完全な実装

(1) リソースの定義

リソース パス 説明
タスクコレクション /api/v1/tasks すべてのタスク
単一タスク /api/v1/tasks/:id 指定タスク
タスクへのコメント /api/v1/tasks/:id/comments 特定のタスクへのコメント
タスクタグ /api/v1/tasks/:id/tags 特定のタスク用のタグ

(2) メソッドのマッピングとリクエスト/レスポンス

JAVASCRIPT
const express = require('express');
const app = express();
app.use(express.json());

let tasks = [
  { id: 1, title: 'Design database schema', status: 'done', priority: 'high', createdAt: '2025-07-01T08:00:00Z' },
  { id: 2, title: 'Implement REST API', status: 'in-progress', priority: 'high', createdAt: '2025-07-02T09:00:00Z' }
];
let nextId = 3;

// GET /api/v1/tasks — Get the task list
app.get('/api/v1/tasks', (req, res) => {
  const { status, priority, page = 1, limit = 20 } = req.query;
  let result = [...tasks];
  if (status) result = result.filter(t => t.status === status);
  if (priority) result = result.filter(t => t.priority === priority);

  const start = (page - 1) * limit;
  const data = result.slice(start, start + Number(limit));

  res.json({
    data,
    pagination: {
      page: Number(page),
      limit: Number(limit),
      total: result.length,
      totalPages: Math.ceil(result.length / Number(limit))
    }
  });
});

// GET /api/v1/tasks/:id — Get a Single Task
app.get('/api/v1/tasks/:id', (req, res) => {
  const task = tasks.find(t => t.id === parseInt(req.params.id));
  if (!task) {
    return res.status(404).json({
      error: 'Task not found',
      details: [{ field: 'id', message: `No task with id ${req.params.id}` }],
      timestamp: new Date().toISOString()
    });
  }
  res.json({
    data: task,
    _links: {
      self: { href: `/api/v1/tasks/${task.id}` },
      update: { href: `/api/v1/tasks/${task.id}`, method: 'PUT' },
      delete: { href: `/api/v1/tasks/${task.id}`, method: 'DELETE' },
      comments: { href: `/api/v1/tasks/${task.id}/comments` }
    }
  });
});

// POST /api/v1/tasks — Create a Task
app.post('/api/v1/tasks', (req, res) => {
  const { title, priority } = req.body;
  if (!title) {
    return res.status(400).json({
      error: 'Validation failed',
      details: [{ field: 'title', message: 'Title is required' }],
      timestamp: new Date().toISOString()
    });
  }
  const task = {
    id: nextId++,
    title,
    status: 'pending',
    priority: priority || 'medium',
    createdAt: new Date().toISOString()
  };
  tasks.push(task);
  res.status(201).location(`/api/v1/tasks/${task.id}`).json({ data: task });
});

// PUT /api/v1/tasks/:id — Full Update
app.put('/api/v1/tasks/:id', (req, res) => {
  const idx = tasks.findIndex(t => t.id === parseInt(req.params.id));
  if (idx === -1) {
    return res.status(404).json({
      error: 'Task not found',
      details: [{ field: 'id', message: `No task with id ${req.params.id}` }],
      timestamp: new Date().toISOString()
    });
  }
  const { title, status, priority } = req.body;
  if (!title || !status) {
    return res.status(400).json({
      error: 'Validation failed',
      details: [
        ...(!title ? [{ field: 'title', message: 'Title is required' }] : []),
        ...(!status ? [{ field: 'status', message: 'Status is required' }] : [])
      ],
      timestamp: new Date().toISOString()
    });
  }
  tasks[idx] = { id: tasks[idx].id, title, status, priority: priority || 'medium', createdAt: tasks[idx].createdAt };
  res.json({ data: tasks[idx] });
});

// PATCH /api/v1/tasks/:id — Partial Update
app.patch('/api/v1/tasks/:id', (req, res) => {
  const task = tasks.find(t => t.id === parseInt(req.params.id));
  if (!task) {
    return res.status(404).json({
      error: 'Task not found',
      details: [{ field: 'id', message: `No task with id ${req.params.id}` }],
      timestamp: new Date().toISOString()
    });
  }
  Object.assign(task, req.body);
  res.json({ data: task });
});

// DELETE /api/v1/tasks/:id — Delete Task
app.delete('/api/v1/tasks/:id', (req, res) => {
  const idx = tasks.findIndex(t => t.id === parseInt(req.params.id));
  if (idx === -1) {
    return res.status(404).json({
      error: 'Task not found',
      details: [{ field: 'id', message: `No task with id ${req.params.id}` }],
      timestamp: new Date().toISOString()
    });
  }
  tasks.splice(idx, 1);
  res.status(204).end();
});

app.listen(3000, () => console.log('Task API running on port 3000'));

(3) リクエストとレスポンスのクイックリファレンス

BASH
# Create a Task
curl -X POST http://localhost:3000/api/v1/tasks \
  -H "Content-Type: application/json" \
  -d '{"title":"Write documentation","priority":"high"}'

# Get the task list(Filter + Pagination)
curl http://localhost:3000/api/v1/tasks?status=pending&page=1&limit=10

# Partial Update
curl -X PATCH http://localhost:3000/api/v1/tasks/1 \
  -H "Content-Type: application/json" \
  -d '{"status":"done"}'

# Delete Task
curl -X DELETE http://localhost:3000/api/v1/tasks/2
TEXT
// POST Created successfully → 201
Status: 201 Created
Location: /api/v1/tasks/3
{ "data": { "id": 3, "title": "Write documentation", "status": "pending", "priority": "high", "createdAt": "2025-07-03T10:30:00Z" } }

// PATCH Update successful → 200
{ "data": { "id": 1, "title": "Design database schema", "status": "done", "priority": "high", "createdAt": "2025-07-01T08:00:00Z" } }

// DELETE Success → 204
Status: 204 No Content
(empty body)

// 404 Error
{ "error": "Task not found", "details": [{ "field": "id", "message": "No task with id 999" }], "timestamp": "2025-07-03T10:30:00Z" }

// 400 Validation Error
{ "error": "Validation failed", "details": [{ "field": "title", "message": "Title is required" }], "timestamp": "2025-07-03T10:30:00Z" }

❓ よくある質問

Q RESTとGraphQLの違いは何ですか?
A RESTはリソースベースであり、各URLは1つのリソースに対応し、HTTPメソッドを用いてそのリソースを操作します。一方、GraphQLはクエリ言語に基づいており、クライアントは単一のエンドポイントから必要に応じてフィールドを取得します。RESTは、リソースが明確に定義されているCRUDシナリオに適しているのに対し、GraphQLは複雑なリレーショナルクエリに適しています。
Q PUTとPATCHの違いは何ですか?
A PUTは完全な置換を行います。リソースのすべてのフィールドを指定する必要があり、指定されていないフィールドはデフォルト値に設定されます。一方、PATCHは部分的な更新を行い、指定したフィールドのみを変更し、指定されていないフィールドは変更されません。PUTは冪等性がありますが、PATCHは冪等性が保証されていません。
Q APIのバージョン管理にはどのような方法が最適ですか?
A URLパスのバージョン管理 (/api/v1/) が最も直感的です。ブラウザで直接テストでき、多くの公開APIで採用されています。リクエストヘッダーによるバージョン管理はよりRESTfulですが、デバッグが複雑になります。クエリパラメータは最もシンプルですが、見落とされがちです。初心者にはURLパスのバージョン管理をお勧めします。
Q REST APIはJSONを返さなければならないのでしょうか?
A 必ずしもそうではありません。RESTでは形式に制限はなく、XML、HTML、JSONなど、さまざまな形式を使用できます。ただし、JSONは軽量で解析が容易であり、JavaScriptとネイティブに互換性があるため、現在最も一般的に使用されている形式です。形式はContent-Typeヘッダーを使用してネゴシエートすることができます。
Q 冪等性とは何ですか?
A 冪等性とは、同じリクエストを1回実行しても、複数回実行しても同じ結果が得られることを指します。GETは冪等性があります(複数回読み取っても同じ結果が得られます)、PUTは冪等性があります(複数回上書きしても同じ結果が得られます)、DELETEは冪等性があります(すでに削除済みのリソースを削除しても、成功が返されます)、そしてPOSTは冪等性を持っていません(実行するたびに新しいリソースが作成されます)。
Q リソース階層が深すぎる場合はどうすればよいですか?
A 階層が2階層を超える場合は、子リソースをトップレベルのリソースに昇格させ、クエリパラメータを使用してリンクすることを検討してください。たとえば、/users/42/tasks/1/comments/5が深すぎる場合は、/comments/5/tasks/1/comments/5に変更することができます。
Q REST API では一括処理をどのように扱うのですか?
A REST には標準的なアプローチはありません。一般的な手法としては、配列を含む POST リクエスト /tasks/batch を使用してレコードを作成すること、配列を含む PATCH リクエスト /tasks を使用してレコードを一括更新すること、および DELETE リクエスト /tasks?ids=1,2,3 を使用してレコードを一括削除することが挙げられます。カスタムエンドポイントは、明確に文書化する必要があります。

📖 まとめ


📝 練習問題

  1. このレッスンにあるすべてのコード例を完成させ、それぞれが正しく動作することを確認してください。
  2. 包括的な例を修正し、独自の拡張機能を追加する
  3. 公式ドキュメントを確認し、このレッスンで取り上げられていないAPIを1~2つ見つけ、それらのテストコードを作成してください。
  4. 振り返り:このレッスンで学んだことを、実際のプロジェクトにどのように活かしますか?
  5. このレッスンで学んだことと、これまでのレッスンの内容を組み合わせて、小さなプロジェクトを作成してみてください。
Web-Tutorial.com

Web-Tutorial 技術チーム

複数の開発者によって共同維持されているプログラミングチュートリアルプラットフォーム。各チュートリアルは専門分野の開発者が執筆・レビューしています。正確で信頼性の高いコンテンツを目指しています — 問題を見つけた場合はお知らせください。

100%