Open2
【Req/Resスキーマ定義】Result型の設計について📝

【Req/Resスキーマ定義】Result型の設計について📝

Result型の設計についてo3に相談📝
Req/Resのスキーマ定義をする際に、
共通のResult型のようなものを作るとしたら、どんな構造がいいですか?
共通 Result 型を設計するときは 「人にも機械にも扱いやすいエンベロープ構造」 を 1 つ決めてしまうと、
API 全体のスキーマが揃い、型定義・ログ・リトライ判定がすべて楽になります。
以下では TypeScript を例に、最低限あるとうれしいフィールドを添えて示します。
(OpenAPI で書く場合も oneOf
で全く同じ形を定義できます。)
1. ガイドライン
ポイント | ねらい |
---|---|
トップレベルで成功/失敗を二分ok: boolean か status: "success" | "error"
|
クライアントは真偽値 1 つで分岐できる |
HTTP ステータスは「通信状態」だけを示す | 200 でも業務エラーを返したいときに混乱しない |
成功時は data と任意の meta |
ページネーションや合計件数などを柔軟に渡せる |
失敗時は error オブジェクト 1 つに集約 |
code (機械判定用)と message (人間向け)を必須に |
全レスポンス共通で requestId と timestamp |
ログ相関やリトライ時の冪等性確認が容易 |
2. 共通 Result 型(TypeScript/Zod でそのまま使える構造)
// --- 基本的なエラー表現 ---
export interface ApiError {
/** 機械判定・翻訳用。例: "VALIDATION_ERROR" */
code: string;
/** エンドユーザーにそのまま見せられるメッセージ */
message: string;
/** 任意: バリデーション一覧やスタックトレースなど */
details?: unknown;
}
// --- Result ジェネリック ---
export type ApiResult<TData = unknown, TMeta = undefined> =
| {
ok: true;
data: TData;
/** 例: ページ情報や集計値 */
meta: TMeta extends undefined ? never : TMeta;
requestId: string;
timestamp: string; // ISO-8601
}
| {
ok: false;
error: ApiError;
requestId: string;
timestamp: string;
};
使い方例
// ユーザー詳細取得
type GetUserRes = ApiResult<{
userId: string;
name: string;
}>;
// 一覧取得(ページネーション付き)
type ListUsersRes = ApiResult<
{ userId: string; name: string }[],
{ page: number; perPage: number; total: number }
>;
3. 実際の JSON 例
成功
{
"ok": true,
"data": {
"userId": "123e4567-e89b-12d3-a456-426614174000",
"name": "Alice"
},
"requestId": "req_05HY...Tx",
"timestamp": "2025-05-12T18:45:02.123Z"
}
失敗
{
"ok": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "メールアドレスは必須です",
"details": [{ "field": "email", "reason": "required" }]
},
"requestId": "req_05HY...Tx",
"timestamp": "2025-05-12T18:45:02.456Z"
}
4. なぜこの形が扱いやすいか
-
型安全
if (res.ok) { /* data にだけアクセス可 */ }
というガードで TypeScript が自動絞り込み。 -
バックエンドの言語を問わない
Rust のResult<T, E>
や Go のerror
パターンとも相性が良く、
シリアライズ時にこの JSON へ簡単に写像できる。 -
ログ&モニタリングが一貫
requestId
とtimestamp
が必ず来るので ELK/Sentry で束ねやすい。 -
フロントのエラー表示・国際化が楽
error.code
を i18n キーとしてそのまま利用でき、メッセージはフォールバックに。
5. 小技・拡張
シーン | 追加フィールド例 | 例 |
---|---|---|
冪等性保証 | idempotencyKey |
同じキーで複数回実行 → 最初の requestId を返す |
リソース生成 | location |
新規作成時に GET /resource/:id の URL を通知 |
リトライ猶予 | retryAfter |
一時的失敗時 (429 / 503) に秒数を示す |
まとめ
- トップレベルで
ok
を真偽値にし、data
/error
を排他にする requestId
とtimestamp
は全レスポンス共通- エラーは
code
,message
,details
に集約
この 3 点を押さえると バックエンド実装・OpenAPI 定義・フロントの型生成 すべてがスムーズになります。