リクエストバリデーション
リクエストバリデーションはKoriの型安全な開発体験の中核です。Koriの拡張可能なバリデーションシステムは、自動型生成を伴う型安全なランタイムバリデーションを提供します - キャストは不要です。Koriのアーキテクチャは異なるバリデーションライブラリをサポートするよう設計されていますが、公式にはファーストクラスのZod統合をすぐに提供しています。
セットアップ
Zod統合パッケージをインストール:
npm install @korix/zod-validator @korix/zod-schema zod
バリデーション付きのKoriアプリケーションをセットアップ:
import { createKori } from '@korix/kori';
import { createKoriZodRequestValidator } from '@korix/zod-validator';
import { zodRequestSchema } from '@korix/zod-schema';
import { z } from 'zod';
const app = createKori({
requestValidator: createKoriZodRequestValidator(),
});
基本例
KoriがAPI開発をどのように変革するか、シンプルなリクエストボディバリデーションから始めて見てみましょう:
const UserSchema = z.object({
name: z.string().min(1),
age: z.number().int().min(0),
});
app.post('/users', {
requestSchema: zodRequestSchema({
body: UserSchema,
}),
handler: (ctx) => {
// 完全に型付けされバリデーション済み!
const user = ctx.req.validatedBody();
return ctx.res.status(201).json({
message: 'User created',
user,
});
},
});
Koriは自動的に処理します:
- リクエストボディの解析とバリデーション
- スキーマからのTypeScript型推論
- ハンドラーに到達する前の無効なリクエストの拒否
すべてのリクエスト部分のバリデーション
Koriは、HTTPリクエストのすべての部分をバリデーションできます:パスパラメータ、クエリパラメータ、ヘッダー、リクエストボディ。
app.put('/users/:id', {
requestSchema: zodRequestSchema({
params: z.object({
id: z.string().regex(/^\d+$/).transform(Number),
}),
queries: z.object({
notify: z
.enum(['true', 'false'])
.transform((val) => val === 'true')
.optional(),
include: z.string().optional(),
}),
headers: z.object({
authorization: z.string().startsWith('Bearer '),
}),
body: z.object({
name: z.string().min(1).optional(),
age: z.number().int().min(0).optional(),
}),
}),
handler: (ctx) => {
// すべてがバリデーション済みで適切に型付け
const { id } = ctx.req.validatedParams();
const { notify, include } = ctx.req.validatedQueries();
const { authorization } = ctx.req.validatedHeaders();
const updates = ctx.req.validatedBody();
return ctx.res.json({
userId: id,
updates,
willNotify: notify ?? false,
token: authorization,
});
},
});
リクエストボディのコンテンツタイプ
デフォルトでは、KoriはJSONとフォームエンコードされたボディをサポートします。異なるコンテンツタイプのスキーマを明示的に定義できます:
const JsonUserSchema = z.object({
name: z.string(),
age: z.number().int().min(0),
});
const FormUserSchema = z.object({
name: z.string(),
// フォームデータの値は文字列なので、必要に応じて変換
age: z.string().transform(Number),
});
app.post('/users', {
requestSchema: zodRequestSchema({
body: {
content: {
'application/json': JsonUserSchema,
'application/x-www-form-urlencoded': FormUserSchema,
},
},
}),
handler: (ctx) => {
const userData = ctx.req.validatedBody();
// 判別可能なユニオンが型安全な処理を可能にする
if (userData.mediaType === 'application/x-www-form-urlencoded') {
// userData.valueはFormUser(number ageを持つ)として型付け
const user = userData.value;
return ctx.res.json({ source: 'form', user });
} else {
// userData.valueはJsonUser(オプションのageを持つ)として型付け
const user = userData.value;
return ctx.res.json({ source: 'json', user });
}
},
});
エラーハンドリング
Koriは複数のレベルのカスタマイゼーションでバリデーション失敗の柔軟なエラーハンドリングを提供します。
デフォルト動作
デフォルトでは、バリデーション失敗は400 Bad Request
レスポンスを返します:
{
"error": {
"type": "BAD_REQUEST",
"message": "Request validation failed"
}
}
コンテンツタイプエラーは、リクエストのコンテンツタイプが定義されたスキーマと一致しない場合に415 Unsupported Media Type
レスポンスを返します:
{
"error": {
"type": "UNSUPPORTED_MEDIA_TYPE",
"message": "Unsupported Media Type"
}
}
カスタムエラーハンドラー
ルートレベルとインスタンスレベルの両方でカスタムエラーハンドラーを提供して、バリデーションエラーレスポンスをカスタマイズできます。
ルートレベルエラーハンドラー
特定のルートのバリデーションエラーを処理:
app.post('/users', {
requestSchema: zodRequestSchema({
body: UserCreateSchema,
}),
onRequestValidationError: (ctx, error) => {
// 詳細なZodバリデーションエラーにアクセス
if (error.body && 'issues' in error.body) {
return ctx.res.badRequest({
message: 'Validation failed',
details: error.body.issues.map((issue) => ({
field: issue.path.join('.'),
message: issue.message,
code: issue.code,
})),
});
}
return ctx.res.badRequest({
message: 'Validation failed',
});
},
handler: (ctx) => {
const user = ctx.req.validatedBody();
// ユーザー作成ロジック...
return ctx.res.json({ message: 'User created', user });
},
});
インスタンスレベルエラーハンドラー
すべてのルートに対するグローバルエラーハンドラーを設定:
const app = createKori({
requestValidator: createKoriZodRequestValidator(),
onRequestValidationError: (ctx, error) => {
// グローバルバリデーションエラーハンドリング
ctx.log().warn('Validation failed', { error });
return ctx.res.status(400).json({
error: 'Invalid request data',
timestamp: new Date().toISOString(),
});
},
});
ハンドラーの優先順位
エラーハンドラーは以下の順序で呼ばれます:
- ルートレベルハンドラー(提供されている場合)
- インスタンスレベルハンドラー(提供されている場合)
- デフォルト動作
各ハンドラーは、レスポンスを返さずに次のハンドラーに渡すことでエラーを処理するか渡すかを選択できます。これにより、特定のハンドラーが特定のエラータイプのみを処理することが可能になります。