1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

REST APIエラーハンドリング設計ガイド ―― 一貫性のある使いやすいAPIのために

Last updated at Posted at 2025-02-22

APIの開発において、適切なエラーハンドリングの設計は非常に重要な要素です。エラーメッセージが不明確だったり、HTTPステータスコードの使い方が不適切だったりすると、APIを利用する開発者の経験を大きく損ない、デバッグ時間の増加やサポートチケットの増加につながってしまいます。本記事では、開発者体験を向上させるための実践的なAPIエラーハンドリング設計について解説します。

下記を参考にしました

想定読者

  • RESTful APIを設計・開発している方
  • APIのエラーハンドリングを改善したい方
  • WebアプリケーションのバックエンドAPIを担当している方

前提知識

  • HTTP/RESTの基本的な知識
  • JSONでのレスポンス形式の理解
  • 基本的なWebセキュリティの知識

APIエラーハンドリングの基本原則

APIのエラーハンドリングには、以下の3つの重要な要素があります:

  1. 適切なHTTPステータスコードの使用:エラーの種類を正確に表現する
  2. 構造化されたエラーレスポンス:一貫性のある明確なフォーマット
  3. セキュアなエラーメッセージ:必要な情報のみを提供し、脆弱性を作らない

それでは、各要素について詳しく見ていきましょう。

HTTPステータスコードの適切な使用

HTTPステータスコードは、エラーの種類を伝える最初の手段です。ステータスコードは適切に使い分けることが重要です。以下に主要なステータスコードの使用例を示します:

クライアントエラー (4xx)

400 Bad Request        - リクエストの形式が不正
401 Unauthorized      - 認証が必要
403 Forbidden        - アクセス権限がない
404 Not Found        - リソースが存在しない
422 Unprocessable Entity - リクエストの形式は正しいが、内容が不正
429 Too Many Requests   - レート制限超過

サーバーエラー (5xx)

500 Internal Server Error - サーバー内部のエラー
503 Service Unavailable  - サービスが一時的に利用不可
504 Gateway Timeout     - 外部サービスとの通信がタイムアウト

構造化されたエラーレスポース

エラーレスポンスは、RFC 9457(Problem Details)仕様に従うことで、一貫性のある分かりやすい形式を実現できます。

{
  "type": "https://api.example.com/errors/invalid-input",
  "title": "入力パラメータが不正です",
  "detail": "メールアドレスは user@domain.com 形式で指定してください"
}

Problem Details形式では、カスタムメンバーを追加して拡張することもできます。クライアントnは認識できない拡張メンバーが含まれている場合には無視するようなスキーマチェックが必要かもしれません:

{
  "type": "https://api.example.com/errors/invalid-input",
  "title": "入力パラメータが不正です",
  "status": 422,
  "detail": "メールアドレスは user@domain.com 形式で指定してください",
  "instance": "/transactions/auth/2024-02-10/125",
  "traceId": "a1b2c3d4-e5f6-7890",  // 拡張メンバー
  "errors": {                        // 拡張メンバー
    "email": ["Invalid format"]
  }
}

各フィールドの意味は以下の通りです:

必須フィールド:

  • type: エラーの種類を示すURI
  • title: エラーの簡潔な説明

オプショナルフィールド:

  • status: HTTPステータスコード
  • detail: エラーの詳細な説明
  • instance: エラーが発生した特定のリソースを示すURI

このレスポンスには以下のヘッダーを付与します:

HTTP/1.1 422 Unprocessable Content
Content-Type: application/problem+json
Content-Language: ja

実装例

では、実際の実装例を見ていきましょう。ここではNode.js/Expressでの実装例を示します:

// カスタムエラークラスの定義
class APIError extends Error {
  constructor(status, title, detail, type) {
    super(detail);
    this.status = status;
    this.title = title;
    this.detail = detail;
    this.type = type || 'https://api.example.com/errors/general-error';
  }
}

// エラーハンドリングミドルウェア
app.use((err, req, res, next) => {
  if (err instanceof APIError) {
    // APIエラーの場合
    res.status(err.status).json({
      type: err.type,
      title: err.title,
      status: err.status,
      detail: err.detail,
      instance: req.originalUrl
    });
  } else {
    // 予期せぬエラーの場合
    console.error(err);
    res.status(500).json({
      type: 'https://api.example.com/errors/internal-error',
      title: 'Internal Server Error',
      status: 500,
      detail: '予期せぬエラーが発生しました',
      instance: req.originalUrl
    });
  }
});

// 使用例
app.post('/users', async (req, res, next) => {
  try {
    const { email } = req.body;
    
    if (!email || !email.includes('@')) {
      throw new APIError(
        422,
        '入力パラメータが不正です',
        'メールアドレスは user@domain.com 形式で指定してください',
        'https://api.example.com/errors/invalid-email'
      );
    }

    // ユーザー作成処理...
    
  } catch (err) {
    next(err);
  }
});

セキュリティに関する考慮事項

エラーメッセージの設計では、セキュリティにも十分な注意を払う必要があります。不適切なエラーメッセージは、攻撃者に有用な情報を提供してしまう可能性があります。例えば:

  • データベースのエラーメッセージから、使用しているDBMSの種類やバージョンが特定される
  • スタックトレースから、使用しているライブラリやそのバージョンが判明する
  • 内部システムの構成やホスト名が露出する
  • 個人情報やセンシティブな業務データが漏洩する

これらの情報は、攻撃の足がかりとして使用される可能性があります。以下のポイントを意識して、適切なエラーメッセージの設計を心がけましょう:

  1. エラーメッセージのSanitization
    エラーメッセージは適切にsanitizeする必要があります:

    • 内部システムの詳細を含めない
    // 危険な例 - 内部情報の漏洩
    throw new APIError(500, 'データベースエラー', 
      'Error executing query: SELECT * FROM users WHERE email = "test@example.com" - Connection refused to db-prod-01.internal:5432');
    
    // 適切な例
    throw new APIError(500, '内部エラー',
      'サービスが一時的に利用できません。しばらく経ってから再度お試しください。');
    
    • スタックトレースを本番環境で表示しない
    // 危険な例 - スタックトレースの漏洩
    app.use((err, req, res, next) => {
      res.status(500).json({
        message: err.message,
        stack: err.stack  // 本番環境で公開すべきでない
      });
    });
    
    // 適切な例
    app.use((err, req, res, next) => {
      const response = {
        type: 'https://api.example.com/errors/internal-error',
        title: '内部エラー'
      };
      
      if (process.env.NODE_ENV === 'development') {
        response.debug = {
          message: err.message,
          stack: err.stack
        };
      }
      
      res.status(500).json(response);
    });
    
    • データベースのエラーメッセージをそのまま返さない
    // 危険な例 - SQLエラーの漏洩
    catch (err) {
      throw new APIError(400, 'Database Error', err.message);
      // 例: "Duplicate entry 'test@example.com' for key 'users.email_unique'"
    }
    
    // 適切な例
    catch (err) {
      if (err.code === 'ER_DUP_ENTRY') {
        throw new APIError(400, '入力エラー', 
          'このメールアドレスは既に登録されています');
      }
      throw new APIError(500, '内部エラー',
        'データの処理中にエラーが発生しました');
    }
    
    • 個人情報(PII)を含めない
    // 危険な例 - 個人情報の漏洩
    throw new APIError(404, 'ユーザーが見つかりません',
      `User ${email} (ID: ${userId}, location: ${userLocation}) not found`);
    
    // 適切な例
    throw new APIError(404, 'ユーザーが見つかりません',
      'アカウントが存在しないか、アクセス権限がありません');
    
  2. 認証関連のエラー

    • ユーザーの存在有無を推測できる情報を含めない
    • 一般的なメッセージを使用する
// 悪い例
if (!user) {
  throw new APIError(401, 'エラー', 'ユーザー test@example.com は存在しません');
}

// 良い例
if (!user) {
  throw new APIError(401, '認証エラー', 'メールアドレスまたはパスワードが正しくありません');
}
  1. エラーメッセージの標準化
    • 一貫性のあるメッセージ形式を使用
    • エラーコードを体系的に管理

エラーハンドリングのテストと監視

適切なエラーハンドリングを維持するためには、テストと監視が欠かせません:

  1. エラーケースのテスト
describe('User API Error Handling', () => {
  it('should return 422 for invalid email format', async () => {
    const response = await request(app)
      .post('/users')
      .send({ email: 'invalid-email' });
    
    expect(response.status).toBe(422);
    expect(response.body).toHaveProperty('type');
    expect(response.body).toHaveProperty('detail');
  });
});
  1. エラー発生率の監視
  • エラーの種類と頻度の追跡
  • 重要なエラーの即時通知
  • エラー解決時間の測定

まとめ

効果的なAPIエラーハンドリングは、以下の要素を組み合わせることで実現できます:

  • HTTPステータスコードの適切な使用
  • 構造化されたエラーレスポンスフォーマット
  • セキュリティを考慮したメッセージング
  • 包括的なテストと監視

これらの原則に従うことで、開発者にとって使いやすく、運用・保守がしやすいAPIを実現できます。

References

Main Article:

Additional Resources:

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?