レスポンスバリデーション
レスポンスバリデーションは、APIが定義されたスキーマに一致するデータを返すことを保証します。これにより、API利用者への型安全性を提供し、開発の早期段階でバグを発見できます。Koriのアーキテクチャは異なるバリデーションライブラリをサポートするよう設計されていますが、公式にはファーストクラスのZod統合を提供し、Standard Schemaもサポートしています。
このガイドではZodを例として使用します。
セットアップ
Zod統合パッケージをインストール:
npm install @korix/zod-schema-adapter zodレスポンスバリデーション付きのKoriアプリケーションをセットアップ:
import { createKori } from '@korix/kori';
import {
zodResponseSchema,
enableZodResponseValidation,
} from '@korix/zod-schema-adapter';
import { z } from 'zod';
const app = createKori({
...enableZodResponseValidation(),
});基本例
異なるステータスコードに対するレスポンススキーマを定義:
const UserSchema = z.object({
id: z.number(),
name: z.string(),
age: z.number().int().min(0),
createdAt: z.string(),
});
const ErrorSchema = z.object({
error: z.object({
type: z.string(),
message: z.string(),
}),
});
app.get('/users/:id', {
responseSchema: zodResponseSchema({
'200': UserSchema,
'404': ErrorSchema,
'500': ErrorSchema,
}),
handler: (ctx) => {
const id = ctx.req.param('id');
const userId = Number(id);
if (userId === 999) {
// この404レスポンスはErrorSchemaに対してバリデーションされる
return ctx.res.notFound({ message: 'User not found' });
}
// この200レスポンスはUserSchemaに対してバリデーションされる
return ctx.res.status(200).json({
id: userId,
name: 'John Doe',
age: 30,
createdAt: new Date().toISOString(),
});
},
});注意:レスポンスバリデーションは、ランタイムでデータのみをチェックします。
ctx.res.json()とスキーマの不一致はTypeScriptでは検出されません。これらはハンドラー完了後に検出されます。
レスポンススキーマパターン
ステータスコードマッチング
レスポンススキーマは複数のステータスコードパターンをサポートします:
app.post('/users', {
responseSchema: zodResponseSchema({
// 正確なステータスコード
'201': UserSchema,
'400': ErrorSchema,
'409': ErrorSchema,
// ワイルドカードパターン(5で始まるステータスコードにマッチ)
'5XX': ErrorSchema,
// 指定されていないステータスコードのデフォルトフォールバック
default: ErrorSchema,
}),
handler: (ctx) => {
// ハンドラーロジック
},
});コンテンツタイプサポート
異なるコンテンツタイプに対して異なるスキーマを定義:
const HtmlErrorSchema = z.string();
const JsonErrorSchema = z.object({
error: z.string(),
details: z.array(z.string()).optional(),
});
app.get('/data', {
responseSchema: zodResponseSchema({
'200': UserSchema,
'400': {
content: {
'application/json': JsonErrorSchema,
'text/html': HtmlErrorSchema,
},
},
}),
handler: (ctx) => {
// ハンドラーロジック
},
});エラーハンドリング
レスポンスバリデーションエラーは、リクエストバリデーションエラーとは異なって処理されます。デフォルトでは、バリデーション失敗はログに記録されますが、クライアントに送信されるレスポンスには影響しません。
デフォルト動作
レスポンスバリデーションが失敗した場合:
- アプリケーションログに警告が記録される
- 元のレスポンスがクライアントに変更なしで送信される
- クライアントにエラーは投げられない
これにより、レスポンスバリデーションの問題がエンドユーザーにとってAPIを壊すことがないよう保証されます。
カスタムエラーハンドラー
カスタムレスポンスバリデーションエラーハンドラーを提供できます:
ルートレベルエラーハンドラー
app.get('/users/:id', {
responseSchema: zodResponseSchema({
'200': UserSchema,
}),
onResponseValidationFailure: (ctx, error) => {
// より多くのコンテキストでバリデーションエラーをログ
ctx.log().error('Response validation failed', {
path: ctx.req.url().pathname,
status: ctx.res.getStatus(),
error,
});
// 任意で異なるレスポンスを返却
return ctx.res.internalError({
message: 'Invalid response format',
});
},
handler: (ctx) => {
// ハンドラーロジック
},
});インスタンスレベルエラーハンドラー
const app = createKori({
...enableZodResponseValidation(),
onResponseValidationFailure: (ctx, error) => {
// グローバルレスポンスバリデーションエラーハンドリング
ctx.log().error('Response validation failed globally', { error });
// 元のレスポンスを使用するためにundefinedを返却
},
});ハンドラーの優先順位
レスポンスバリデーションエラーハンドラーは、リクエストバリデーションと同じ優先順位に従います:
- ルートレベルハンドラー(提供されている場合)
- インスタンスレベルハンドラー(提供されている場合)
- デフォルト動作(警告をログ、元のレスポンスを送信)
各ハンドラーは、レスポンスを返さずに次のハンドラーに渡すことでエラーを処理するか渡すかを選択できます。これにより、特定のハンドラーが特定のエラータイプのみを処理することが可能になります。
ストリームレスポンスの処理
レスポンスバリデーションは、クライアントに送信される前にバリデーションできないため、ストリーミングレスポンスのバリデーションを自動的にスキップします。
app.get('/download', {
responseSchema: zodResponseSchema({
'200': z.string(), // これはストリームに対してバリデーションされない
}),
handler: (ctx) => {
// ストリーミングレスポンスはバリデーションされない
return ctx.res.stream(someReadableStream);
},
});