ストーリー
高橋アーキテクトが画面を指差した。そこには 500 Internal Server Error と表示されている。
エラーレスポンスの設計
RFC 7807: Problem Details
// RFC 7807(Problem Details for HTTP APIs)に基づく標準形式
interface ProblemDetails {
type: string; // エラーの種類を識別するURI
title: string; // 人間が読めるエラータイトル
status: number; // HTTPステータスコード
detail: string; // エラーの詳細説明
instance: string; // このエラーが発生した具体的なURI
}
// 実装例
const errorResponse = {
type: "https://api.example.com/errors/validation",
title: "Validation Error",
status: 422,
detail: "リクエストの入力値に問題があります",
instance: "/api/v1/users",
// 拡張フィールド(RFC 7807で許容されている)
errors: [
{
field: "email",
message: "メールアドレスの形式が正しくありません",
value: "not-an-email"
},
{
field: "age",
message: "年齢は0以上の整数を指定してください",
value: -5
}
],
traceId: "req_abc123def456"
};
エラーレスポンスの構造
// 統一されたエラーレスポンス型
interface ApiErrorResponse {
error: {
code: string; // 機械可読なエラーコード
message: string; // ユーザー向けメッセージ
details?: ErrorDetail[]; // 詳細なエラー情報
traceId: string; // トレース用ID
};
}
interface ErrorDetail {
field?: string; // エラーが発生したフィールド
message: string; // 詳細メッセージ
value?: unknown; // 送信された値
}
// 使用例
// 400 Bad Request
{
"error": {
"code": "INVALID_REQUEST",
"message": "リクエストの形式が正しくありません",
"traceId": "req_abc123"
}
}
// 422 Unprocessable Entity
{
"error": {
"code": "VALIDATION_ERROR",
"message": "入力内容に問題があります",
"details": [
{ "field": "email", "message": "必須項目です" },
{ "field": "password", "message": "8文字以上必要です", "value": "***" }
],
"traceId": "req_def456"
}
}
エラーカテゴリとステータスコードの対応
// エラーコードの体系化
const ERROR_CODES = {
// 認証・認可エラー (401, 403)
UNAUTHENTICATED: { status: 401, message: '認証が必要です' },
TOKEN_EXPIRED: { status: 401, message: 'トークンが期限切れです' },
FORBIDDEN: { status: 403, message: 'この操作を行う権限がありません' },
// バリデーションエラー (400, 422)
INVALID_REQUEST: { status: 400, message: 'リクエストの形式が不正です' },
VALIDATION_ERROR: { status: 422, message: '入力内容に問題があります' },
// リソースエラー (404, 409)
NOT_FOUND: { status: 404, message: 'リソースが見つかりません' },
ALREADY_EXISTS: { status: 409, message: '既に存在するリソースです' },
// レート制限 (429)
RATE_LIMITED: { status: 429, message: 'リクエスト数が制限を超えました' },
// サーバーエラー (500, 503)
INTERNAL_ERROR: { status: 500, message: 'サーバー内部エラーが発生しました' },
SERVICE_UNAVAILABLE: { status: 503, message: 'サービスは一時的に利用できません' },
} as const;
セキュリティとエラーメッセージ
情報漏洩を防ぐ
// 悪い例: 内部情報を漏らす
{
"error": {
"message": "PostgreSQL error: relation \"users\" does not exist",
"stack": "Error at UserRepository.findById (/app/src/repos/user.ts:42)"
}
}
// 良い例: 必要最小限の情報
{
"error": {
"code": "INTERNAL_ERROR",
"message": "サーバー内部エラーが発生しました。問題が続く場合はサポートにお問い合わせください。",
"traceId": "req_abc123"
}
}
// 詳細なエラーはサーバーログに記録し、traceIdで追跡する
認証エラーの注意点
// 悪い例: ユーザーの存在を漏らす
POST /api/v1/login
// → "このメールアドレスは登録されていません" // アカウントの存在が判明
// → "パスワードが間違っています" // メールアドレスは正しいと判明
// 良い例: 曖昧なメッセージ
POST /api/v1/login
// → "メールアドレスまたはパスワードが正しくありません"
まとめ
| ポイント | 内容 |
|---|---|
| 標準形式 | RFC 7807(Problem Details)を採用する |
| エラー構造 | code, message, details, traceId を含める |
| ステータスコード | エラーの種類に応じて適切なコードを返す |
| セキュリティ | 内部情報を漏らさない、traceIdで追跡 |
| 一貫性 | 全エンドポイントで同じエラー形式を使用 |
チェックリスト
- RFC 7807 Problem Details の形式を理解した
- エラーレスポンスの統一構造を設計できる
- セキュリティを考慮したエラーメッセージの重要性を理解した
- traceId によるエラー追跡の仕組みを把握した
次のステップへ
エラーハンドリングの設計を学びました。
次は Step 1 の理解度チェックです。 ここまで学んだREST APIの基本原則、ベストプラクティス、エラーハンドリングの知識を確認しましょう。
推定読了時間: 15分