Valibot の使い方 — モジュラー設計で軽量な型安全バリデーションライブラリ
一言でいうと
Valibot は、モジュラー設計によりツリーシェイキングを最大限に活かし、最小700バイト未満からバンドルサイズを始められる型安全なスキーマバリデーションライブラリです。Zod と似た開発体験を提供しつつ、バンドルサイズを最大95%削減できます。
どんな時に使う?
- APIリクエスト/レスポンスのバリデーション — サーバーサイドで受け取るJSONの構造を実行時に検証し、型安全に扱いたい場合
- フォームバリデーション — React Hook Form や SvelteKit などのフォームライブラリと組み合わせて、クライアントサイドで入力値を検証する場合
- 設定ファイルや環境変数のパース —
process.envや外部設定ファイルの値を型安全に読み込みたい場合
特にバンドルサイズが重要なフロントエンドアプリケーションや、Edge Functions のようなサイズ制約のある環境で真価を発揮します。
インストール
# npm
npm install valibot
# yarn
yarn add valibot
# pnpm
pnpm add valibot
※ 本記事は valibot v1.3.1 時点の情報に基づいています。
基本的な使い方
最も典型的なパターンとして、オブジェクトスキーマを定義してデータをパースする例を示します。
import * as v from 'valibot';
// スキーマ定義
const UserSchema = v.object({
name: v.pipe(v.string(), v.minLength(1, '名前は必須です')),
email: v.pipe(v.string(), v.email('有効なメールアドレスを入力してください')),
age: v.pipe(v.number(), v.minValue(0), v.maxValue(150)),
});
// スキーマから TypeScript の型を推論
type User = v.InferOutput<typeof UserSchema>;
// => { name: string; email: string; age: number }
// バリデーション実行(失敗時は例外をスロー)
const user = v.parse(UserSchema, {
name: '田中太郎',
email: 'tanaka@example.com',
age: 30,
});
console.log(user); // { name: '田中太郎', email: 'tanaka@example.com', age: 30 }
v.pipe() は Valibot の中核的な概念で、スキーマに対してバリデーションや変換のアクション(パイプライン)を連鎖的に適用します。
よく使うAPI
1. safeParse — 例外を投げないバリデーション
import * as v from 'valibot';
const EmailSchema = v.pipe(v.string(), v.email());
const result = v.safeParse(EmailSchema, 'invalid-email');
if (result.success) {
console.log(result.output); // string 型として利用可能
} else {
console.log(result.issues); // バリデーションエラーの詳細配列
// [{ message: 'Invalid email', path: undefined, ... }]
}
parse と異なり例外をスローしないため、エラーハンドリングを制御フローで行いたい場合に適しています。
2. pipe — バリデーション・変換のパイプライン
import * as v from 'valibot';
// 文字列を受け取り → トリム → 小文字化 → メール形式チェック
const NormalizedEmailSchema = v.pipe(
v.string(),
v.trim(),
v.toLowerCase(),
v.email(),
);
const email = v.parse(NormalizedEmailSchema, ' TANAKA@Example.COM ');
console.log(email); // 'tanaka@example.com'
pipe はスキーマを第1引数に取り、以降にバリデーションアクションや変換アクションを任意の数だけ連鎖できます。
3. optional / nullable / nullish — オプショナルな値の扱い
import * as v from 'valibot';
const ProfileSchema = v.object({
nickname: v.optional(v.string()), // string | undefined
bio: v.nullable(v.string()), // string | null
website: v.nullish(v.string()), // string | null | undefined
role: v.optional(v.string(), 'member'), // デフォルト値付き
});
type Profile = v.InferOutput<typeof ProfileSchema>;
// {
// nickname?: string | undefined;
// bio: string | null;
// website?: string | null | undefined;
// role: string; ← デフォルト値があるため undefined にならない
// }
const profile = v.parse(ProfileSchema, { bio: null });
console.log(profile.role); // 'member'
4. array / union / variant — 複合型の定義
import * as v from 'valibot';
// 配列スキーマ
const TagsSchema = v.pipe(
v.array(v.pipe(v.string(), v.minLength(1))),
v.minLength(1, 'タグは1つ以上必要です'),
v.maxLength(10),
);
// ユニオン型(判別なし)
const StringOrNumberSchema = v.union([v.string(), v.number()]);
// 判別付きユニオン(Discriminated Union)
const EventSchema = v.variant('type', [
v.object({
type: v.literal('click'),
x: v.number(),
y: v.number(),
}),
v.object({
type: v.literal('keydown'),
key: v.string(),
}),
]);
type Event = v.InferOutput<typeof EventSchema>;
// { type: 'click'; x: number; y: number } | { type: 'keydown'; key: string }
const event = v.parse(EventSchema, { type: 'click', x: 100, y: 200 });
variant は判別フィールドを指定することで、union よりも効率的かつ正確にバリデーションを行います。
5. flatten — エラーメッセージの整形
import * as v from 'valibot';
const FormSchema = v.object({
email: v.pipe(v.string(), v.email('有効なメールアドレスを入力してください')),
password: v.pipe(
v.string(),
v.minLength(8, 'パスワードは8文字以上必要です'),
v.regex(/[A-Z]/, '大文字を1文字以上含めてください'),
),
});
const result = v.safeParse(FormSchema, { email: 'bad', password: 'short' });
if (!result.success) {
const flat = v.flatten(result.issues);
console.log(flat);
// {
// nested: {
// email: ['有効なメールアドレスを入力してください'],
// password: ['パスワードは8文字以上必要です', '大文字を1文字以上含めてください'],
// }
// }
}
flatten はネストされた issues 配列をフィールド名ごとのメッセージ配列に変換してくれるため、フォームUIへのエラー表示に便利です。
類似パッケージとの比較
| 特徴 | Valibot | Zod | Yup | ArkType |
|---|---|---|---|---|
| バンドルサイズ(min+gzip) | ~1 kB〜(使用分のみ) | ~14 kB | ~15 kB | ~5 kB |
| ツリーシェイキング | ◎(モジュラー設計) | △(クラスベース) | △ | ○ |
| TypeScript 型推論 | ◎ | ◎ | ○ | ◎ |
| API スタイル | 関数ベース + pipe | メソッドチェーン | メソッドチェーン | 文字列ベースDSL |
| エコシステム成熟度 | ○(急成長中) | ◎(デファクト) | ◎(歴史あり) | △ |
| 依存関係 | 0 | 0 | 数個 | 0 |
Zod からの移行を検討する場合: API の概念は似ていますが、Zod のメソッドチェーン(z.string().email())が Valibot では関数合成(v.pipe(v.string(), v.email()))になる点が最大の違いです。公式に Migration Guide が用意されています。
注意点・Tips
ツリーシェイキングの恩恵を最大化する
// ✅ 推奨:名前付きインポートでも動作するが、名前空間インポートが公式推奨
import * as v from 'valibot';
// ✅ これも OK
import { object, string, parse } from 'valibot';
どちらの書き方でもツリーシェイキングは有効です。import * でも未使用の関数はバンドルから除外されます。
pipe の順序に注意
import * as v from 'valibot';
// ✅ 正しい:変換 → バリデーションの順序
const Schema = v.pipe(v.string(), v.trim(), v.minLength(1));
// ⚠️ 意図しない結果:バリデーション → 変換の順序だと
// 空白のみの文字列が minLength(1) を通過してしまう
const BadSchema = v.pipe(v.string(), v.minLength(1), v.trim());
pipe 内のアクションは左から右へ順に実行されます。変換(trim, toLowerCase など)をバリデーションの前に配置するのが一般的なベストプラクティスです。
InferInput と InferOutput の使い分け
import * as v from 'valibot';
const Schema = v.object({
createdAt: v.pipe(v.string(), v.isoTimestamp(), v.transform((s) => new Date(s))),
});
// 入力側の型(変換前)
type Input = v.InferInput<typeof Schema>;
// { createdAt: string }
// 出力側の型(変換後)
type Output = v.InferOutput<typeof Schema>;
// { createdAt: Date }
transform を使う場合、入力と出力で型が異なります。フォームの入力値には InferInput、パース後のデータには InferOutput を使い分けてください。
React Hook Form との統合
import { useForm } from 'react-hook-form';
import { valibotResolver } from '@hookform/resolvers/valibot';
import * as v from 'valibot';
const LoginSchema = v.object({
email: v.pipe(v.string(), v.email()),
password: v.pipe(v.string(), v.minLength(8)),
});
function LoginForm() {
const { register, handleSubmit, formState: { errors } } = useForm({
resolver: valibotResolver(LoginSchema),
});
return (
<form onSubmit={handleSubmit((data) => console.log(data))}>
<input {...register('email')} />
{errors.email && <span>{errors.email.message}</span>}
<input type="password" {...register('password')} />
{errors.password && <span>{errors.password.message}</span>}
<button type="submit">ログイン</button>
</form>
);
}
@hookform/resolvers パッケージが Valibot をサポートしているため、追加設定なしで統合できます。
まとめ
Valibot は、Zod と同等の型安全なバリデーション体験を提供しつつ、モジュラー設計によりバンドルサイズを劇的に削減できるライブラリです。pipe による関数合成スタイルに慣れれば、柔軟かつ読みやすいスキーマ定義が可能になります。バンドルサイズを気にするフロントエンドプロジェクトや、Edge Runtime のような制約のある環境では、Zod からの乗り換え先として有力な選択肢です。