メインコンテンツまでスキップ

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-schemavalibotのバージョンと一致させる必要があります。例:

  • valibot@1.4.x@valibot/to-json-schema@1.7.x
  • valibot@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オプションは、リクエストボディの形式を指定します:

TargetContent-TypeUse Case
'json'(デフォルト)application/jsonJSON APIリクエスト
'form'multipart/form-dataapplication/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を組み立てられる