<CodeLearn/>
Node.js レッスン4

REST API設計

CRUD操作とHTTPメソッドでAPIを設計しよう

RESTとは?

REST(Representational State Transfer)は、 Web APIを設計するためのアーキテクチャスタイルです。 HTTPメソッドとURLの組み合わせでリソース(データ)を操作します。

RESTの原則

  • • URLはリソース(名詞)を表す
  • • HTTPメソッドで操作(動詞)を表す
  • • ステートレス(状態を持たない)
  • • 統一されたインターフェース

URL設計の例

  • GET /api/books — 一覧取得
  • GET /api/books/1 — 詳細取得
  • POST /api/books — 新規作成
  • PUT /api/books/1 — 更新
  • DELETE /api/books/1 — 削除

HTTPメソッドとステータスコード

REST APIでは、HTTPメソッドで操作の種類を、ステータスコードで結果を表現します。

// === HTTPメソッド ===
// GET     - リソースの取得(べき等・安全)
// POST    - リソースの作成
// PUT     - リソースの全体更新(べき等)
// PATCH   - リソースの部分更新
// DELETE  - リソースの削除(べき等)

// === よく使うステータスコード ===
// 200 OK              - リクエスト成功
// 201 Created         - リソース作成成功
// 204 No Content      - 成功(レスポンスボディなし)
// 400 Bad Request     - クライアントのリクエストが不正
// 401 Unauthorized    - 認証が必要
// 403 Forbidden       - アクセス権限がない
// 404 Not Found       - リソースが見つからない
// 409 Conflict        - リソースの競合
// 422 Unprocessable   - バリデーションエラー
// 500 Internal Error  - サーバー内部エラー

CRUD操作の実装

書籍管理APIを例に、CRUD(Create, Read, Update, Delete)操作を実装してみましょう。

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

app.use(express.json());

// 仮のデータベース(メモリ上)
let books = [
  { id: 1, title: 'JavaScript入門', author: '山田太郎', price: 2800 },
  { id: 2, title: 'Node.js実践ガイド', author: '鈴木花子', price: 3200 },
  { id: 3, title: 'Web開発の教科書', author: '田中一郎', price: 2500 },
];
let nextId = 4;

// CREATE: 書籍を新規作成
app.post('/api/books', (req, res) => {
  const { title, author, price } = req.body;

  // バリデーション
  if (!title || !author) {
    return res.status(400).json({
      error: 'title と author は必須です',
    });
  }

  const newBook = {
    id: nextId++,
    title,
    author,
    price: price || 0,
  };
  books.push(newBook);

  res.status(201).json(newBook);
});

// READ: 書籍一覧を取得
app.get('/api/books', (req, res) => {
  // クエリパラメータでフィルタリング
  const { author, minPrice } = req.query;

  let result = books;
  if (author) {
    result = result.filter(b => b.author.includes(author));
  }
  if (minPrice) {
    result = result.filter(b => b.price >= Number(minPrice));
  }

  res.json(result);
});

// READ: 書籍を1件取得
app.get('/api/books/:id', (req, res) => {
  const book = books.find(b => b.id === Number(req.params.id));

  if (!book) {
    return res.status(404).json({ error: '書籍が見つかりません' });
  }

  res.json(book);
});

// UPDATE: 書籍を更新
app.put('/api/books/:id', (req, res) => {
  const index = books.findIndex(b => b.id === Number(req.params.id));

  if (index === -1) {
    return res.status(404).json({ error: '書籍が見つかりません' });
  }

  const { title, author, price } = req.body;
  books[index] = {
    ...books[index],
    title: title ?? books[index].title,
    author: author ?? books[index].author,
    price: price ?? books[index].price,
  };

  res.json(books[index]);
});

// DELETE: 書籍を削除
app.delete('/api/books/:id', (req, res) => {
  const index = books.findIndex(b => b.id === Number(req.params.id));

  if (index === -1) {
    return res.status(404).json({ error: '書籍が見つかりません' });
  }

  books.splice(index, 1);
  res.status(204).send();
});

app.listen(3000);

リクエストボディのパースとバリデーション

POSTやPUTリクエストではクライアントからデータを受け取ります。 不正なデータからアプリを守るためにバリデーションが重要です。

// express.json() でJSONボディをパース
app.use(express.json());

// URLエンコードされたデータもパース(フォーム送信等)
app.use(express.urlencoded({ extended: true }));

// 手動バリデーションの例
function validateBook(data) {
  const errors = [];

  if (!data.title || typeof data.title !== 'string') {
    errors.push('title は文字列で必須です');
  }
  if (!data.author || typeof data.author !== 'string') {
    errors.push('author は文字列で必須です');
  }
  if (data.price !== undefined && typeof data.price !== 'number') {
    errors.push('price は数値で指定してください');
  }
  if (data.price !== undefined && data.price < 0) {
    errors.push('price は0以上で指定してください');
  }

  return errors;
}

app.post('/api/books', (req, res) => {
  const errors = validateBook(req.body);

  if (errors.length > 0) {
    return res.status(422).json({ errors });
  }

  // バリデーション通過 → 作成処理
  const newBook = {
    id: nextId++,
    title: req.body.title.trim(),
    author: req.body.author.trim(),
    price: req.body.price || 0,
  };
  books.push(newBook);
  res.status(201).json(newBook);
});

API設計のベストプラクティス

使いやすく、保守しやすいAPIを作るためのポイントを押さえましょう。

// 1. URLは名詞・複数形を使う
// Good:  /api/users, /api/books
// Bad:   /api/getUsers, /api/book

// 2. ネストでリレーションを表現
// GET /api/users/1/posts     ← ユーザー1の投稿一覧
// GET /api/posts/5/comments  ← 投稿5のコメント一覧

// 3. ページネーション
app.get('/api/books', (req, res) => {
  const page = parseInt(req.query.page) || 1;
  const limit = parseInt(req.query.limit) || 10;
  const start = (page - 1) * limit;

  const paginatedBooks = books.slice(start, start + limit);

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

// 4. 統一されたエラーレスポンス
// {
//   "error": {
//     "code": "NOT_FOUND",
//     "message": "書籍が見つかりません"
//   }
// }

// 5. APIバージョニング
// /api/v1/users
// /api/v2/users

まとめ

  • RESTはHTTPメソッド(GET/POST/PUT/DELETE)でリソースを操作するアーキテクチャ
  • 適切なステータスコード(200, 201, 404, 500等)で結果を伝える
  • CRUD操作はWebアプリの基本パターン
  • バリデーションで不正なデータからアプリを守る
  • URL設計はリソース名(名詞・複数形)を使い、ページネーションやバージョニングを考慮する