REST APIはほぼすべてのWebサービスの基盤です。しかし、「動く」APIと「良い」APIには大きな違いがあります。本記事では、保守性が高く、使いやすいREST APIを設計するための実践的なベストプラクティスを解説します。

1. RESTの基本原則

  • リソース指向:URLはリソース(名詞)を表し、操作(動詞)はHTTPメソッドで表現
  • ステートレス:各リクエストは独立しており、サーバーにセッション状態を持たない
  • 統一インターフェース:一貫した規約に従い、予測可能なAPIを提供
  • HATEOAS:レスポンスに次に取れるアクションのリンクを含める

2. エンドポイント命名規則

# ✅ 良い例(リソース名は複数形の名詞)
GET    /api/v1/users          # ユーザー一覧
GET    /api/v1/users/123      # ユーザー詳細
POST   /api/v1/users          # ユーザー作成
PUT    /api/v1/users/123      # ユーザー更新(全体)
PATCH  /api/v1/users/123      # ユーザー更新(部分)
DELETE /api/v1/users/123      # ユーザー削除

# ✅ ネストされたリソース
GET    /api/v1/users/123/posts      # ユーザーの投稿一覧
GET    /api/v1/users/123/posts/456  # 特定の投稿

# ❌ 悪い例(動詞を使っている)
GET    /api/v1/getUsers
POST   /api/v1/createUser
DELETE /api/v1/deleteUser/123

# ❌ 悪い例(camelCase、単数形)
GET    /api/v1/userList
GET    /api/v1/user/123
✅ 命名のルール

URLにはkebab-case(ハイフン区切り)を使い、クエリパラメータにはcamelCaseを使うのが一般的です。例:/api/v1/blog-posts?sortBy=createdAt

3. HTTPメソッドの正しい使い方

// Express.jsでの実装例
const express = require('express');
const router = express.Router();

// GET - リソースの取得(冪等・安全)
router.get('/users', async (req, res) => {
  const { page = 1, limit = 20, sortBy = 'createdAt' } = req.query;
  const users = await User.find()
    .sort(sortBy)
    .skip((page - 1) * limit)
    .limit(limit);

  res.json({
    data: users,
    pagination: { page, limit, total: await User.countDocuments() }
  });
});

// POST - リソースの作成(冪等ではない)
router.post('/users', async (req, res) => {
  const user = await User.create(req.body);
  res.status(201).json({ data: user });
});

// PUT - リソースの全体更新(冪等)
router.put('/users/:id', async (req, res) => {
  const user = await User.findByIdAndUpdate(req.params.id, req.body, {
    new: true, runValidators: true
  });
  if (!user) return res.status(404).json({ error: 'User not found' });
  res.json({ data: user });
});

// DELETE - リソースの削除(冪等)
router.delete('/users/:id', async (req, res) => {
  const user = await User.findByIdAndDelete(req.params.id);
  if (!user) return res.status(404).json({ error: 'User not found' });
  res.status(204).send();
});
広告

4. ステータスコードの使い分け

  • 200 OK — リクエスト成功(GET、PUT、PATCH)
  • 201 Created — リソース作成成功(POST)
  • 204 No Content — 成功だがレスポンスボディなし(DELETE)
  • 400 Bad Request — バリデーションエラー
  • 401 Unauthorized — 認証が必要
  • 403 Forbidden — 権限不足
  • 404 Not Found — リソースが見つからない
  • 409 Conflict — リソースの競合(重複Eメール等)
  • 422 Unprocessable Entity — バリデーションエラー(詳細)
  • 429 Too Many Requests — レート制限
  • 500 Internal Server Error — サーバーエラー

統一的なエラーレスポンス

{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "入力データにエラーがあります",
    "details": [
      { "field": "email", "message": "有効なメールアドレスを入力してください" },
      { "field": "password", "message": "パスワードは8文字以上必要です" }
    ]
  }
}

5. 認証とセキュリティ

// JWT認証ミドルウェア
const jwt = require('jsonwebtoken');

const authenticate = (req, res, next) => {
  const token = req.headers.authorization?.split(' ')[1]; // Bearer TOKEN
  if (!token) {
    return res.status(401).json({ error: { code: 'UNAUTHORIZED' }});
  }

  try {
    const decoded = jwt.verify(token, process.env.JWT_SECRET);
    req.user = decoded;
    next();
  } catch (err) {
    return res.status(401).json({ error: { code: 'INVALID_TOKEN' }});
  }
};

// レート制限
const rateLimit = require('express-rate-limit');
const apiLimiter = rateLimit({
  windowMs: 15 * 60 * 1000,  // 15分間
  max: 100,                   // 最大100リクエスト
  standardHeaders: true,
  legacyHeaders: false,
});

6. バージョニング戦略

  • URLパスバージョニング(推奨):/api/v1/users/api/v2/users
  • ヘッダーバージョニングAccept: application/vnd.myapi.v2+json
  • クエリパラメータ/api/users?version=2

良いAPI設計は、チームの生産性とシステムの保守性を大きく左右します。この記事のベストプラクティスを参考に、使いやすく拡張性の高いAPIを目指してください。