Validation
Zeltは、同期的なStandard Schema互換のschemaを使ってリクエストボディをバリデーションします。schema["~standard"].validate(value)を公開しているバリデータであれば、Valibot、Zod、ArkTypeを含め、どれでも使用できます。
Installation
request()は@zeltjs/coreに含まれています。使用したいschemaライブラリをインストールしてください:
pnpm add @zeltjs/core valibot
With OpenAPI Generation
実行時のバリデーションにはStandard Schemaだけで十分です。schemaが標準JSON Schemaを公開していない場合、OpenAPI生成にはschemaアダプターが別途必要です。Valibotの場合は、@zeltjs/validator-valibot/openapiと@valibot/to-json-schemaを使います:
pnpm add @zeltjs/validator-valibot valibot @valibot/to-json-schema
@valibot/to-json-schemaはvalibotのバージョンと一致させる必要があります。例:
valibot@1.4.x→@valibot/to-json-schema@1.7.xvalibot@1.3.x→@valibot/to-json-schema@1.6.x
互換性についてはValibotのリリースページを確認してください。
request()は@zeltjs/coreからimportしてください。Valibotパッケージが提供するのはOpenAPI schemaアダプターのみです。
valibotのpeer dependencyは^1.0.0である必要があります。テストは1.3.xに対して行っています。それより古いバージョンを使うと型推論の問題が発生する場合があります。
Basic Usage
Valibot schemaを渡したrequest()を使い、リクエストボディをバリデーションします:
import { Controller, Post, request, response } from '@zeltjs/core';
import * as v from 'valibot';
const CreateUserSchema = v.object({
name: v.pipe(v.string(), v.minLength(1), v.maxLength(100)),
email: v.pipe(v.string(), v.email()),
age: v.optional(v.pipe(v.number(), v.minValue(0), v.maxValue(150))),
});
@Controller('/users')
export class UserController {
@Post('/')
async create(req = request(CreateUserSchema), res = response()) {
const body = await req.body();
// bodyは { name: string; email: string; age?: number } として完全に型付けされる
return res.json({ id: '1', ...body }, 201);
}
}
Form Data and File Uploads
ファイルアップロードを含むmultipart/form-dataリクエストをバリデーションするにはrequest(schema, { target: 'form' })を使います:
import { Controller, Post, request, response } from '@zeltjs/core';
import * as v from 'valibot';
const UploadSchema = v.object({
file: v.instance(File),
description: v.optional(v.string()),
});
@Controller('/upload')
export class UploadController {
@Post('/')
async upload(req = request(UploadSchema, { target: 'form' }), res = response()) {
const body = await req.body();
// body.fileはFileオブジェクトである
console.log(body.file.name, body.file.size, body.file.type);
return res.json({ filename: body.file.name, size: body.file.size }, 201);
}
}
Target Options
request()のtargetオプションは、リクエストボディの形式を指定します:
| Target | Content-Type | Use Case |
|---|---|---|
'json'(デフォルト) | application/json | JSON APIリクエスト |
'form' | multipart/form-data、application/x-www-form-urlencoded | ファイルアップロード、HTMLフォーム |
Multiple Files
const MultiUploadSchema = v.object({
files: v.array(v.instance(File)),
category: v.string(),
});
@Controller('/upload')
class BulkUploadController {
@Post('/bulk')
async bulkUpload(req = request(MultiUploadSchema, { target: 'form' })) {
const body = await req.body();
for (const file of body.files) {
console.log(file.name);
}
return { count: body.files.length };
}
}
OpenAPI Generation
'form' targetを使う場合、OpenAPI出力は自動的にcontent typeとしてmultipart/form-dataを使用します:
requestBody:
required: true
content:
multipart/form-data:
schema:
$ref: '#/components/schemas/UploadSchema'
Validation Error Response
バリデーションが失敗すると、Zeltは自動的に400レスポンスを返します:
{
"code": "VALIDATION_FAILED",
"issues": [
{
"kind": "validation",
"type": "email",
"message": "Invalid email",
"path": ["email"]
}
]
}
エラーレスポンスの詳細はError Handlingを参照してください。
Common Validations
String Validations
const schema = v.object({
username: v.pipe(
v.string(),
v.minLength(3),
v.maxLength(20),
v.regex(/^[a-z0-9_]+$/i)
),
email: v.pipe(v.string(), v.email()),
url: v.pipe(v.string(), v.url()),
uuid: v.pipe(v.string(), v.uuid()),
});
Number Validations
const schema = v.object({
age: v.pipe(v.number(), v.minValue(0), v.maxValue(150)),
price: v.pipe(v.number(), v.minValue(0)),
quantity: v.pipe(v.number(), v.integer(), v.minValue(1)),
});
Array Validations
const schema = v.object({
tags: v.pipe(
v.array(v.string()),
v.minLength(1),
v.maxLength(10)
),
scores: v.array(v.pipe(v.number(), v.minValue(0), v.maxValue(100))),
});
Optional and Nullable
const schema = v.object({
required: v.string(),
optional: v.optional(v.string()),
nullable: v.nullable(v.string()),
optionalNullable: v.optional(v.nullable(v.string())),
withDefault: v.optional(v.string(), 'default value'),
});
Nested Objects
const AddressSchema = v.object({
street: v.string(),
city: v.string(),
country: v.string(),
zipCode: v.optional(v.string()),
});
const UserSchema = v.object({
name: v.string(),
address: AddressSchema,
alternateAddresses: v.optional(v.array(AddressSchema)),
});
Type Inference
Valibot schemaは自動的なTypeScript型推論を提供します:
const UserSchema = v.object({
name: v.string(),
age: v.number(),
});
// schemaから型を推論する
type User = v.InferOutput<typeof UserSchema>;
// { name: string; age: number } と同等
Why Valibot?
- Type-safe — 自動型推論を含むフルTypeScriptサポート
- Lightweight — tree-shakeable、使う分だけを含む
- Fast — 実行時性能に最適化
- Composable — シンプルな構成要素から複雑なschemaを組み立てられる