AI

フォームバリデーション比較:Zod vs Yup vs Valibot でTypeScriptのバリデーションを実装する

オープンソースラボ編集部2026年6月14日

フォームバリデーション比較:Zod vs Yup vs Valibot でTypeScriptのバリデーションを実装する

フォームバリデーション・APIリクエスト検証・設定ファイルのスキーマチェックに使われるTypeScript/JavaScriptのランタイムバリデーションライブラリは開発の基盤ツールです。Zod(TypeScript First・最大シェア)・Yup(JavaScript製・Formik連携の老舗)・Valibot(超軽量・モジュラー)の3つが2026年の主要バリデーションライブラリです。

バリデーションライブラリを使う理由

  • 型安全: TypeScriptの型定義とランタイムバリデーションを一元管理(二重定義を排除)
  • API境界保護: 外部からのリクエストボディをスキーマで検証してSQLインジェクション・型エラーを防止
  • エラーメッセージ管理: バリデーションエラーの国際化・カスタムメッセージを宣言的に定義
  • スキーマ共有: フロントエンドとバックエンドで同一スキーマを再利用してDRYを実現

主要ライブラリの概要

Zod

2020年公開、TypeScript製のOSSです。GitHubスター35k+。TypeScript First設計で最もシェアが高いバリデーションライブラリです。スキーマから自動的にTypeScript型を推論でき、React Hook Form・tRPC・Prismaとのエコシステム統合が最も充実しています。

// Zod: 包括的なスキーマ定義と型推論
import { z } from 'zod'

// 基本スキーマ定義
const UserSchema = z.object({
  id: z.string().uuid(),
  name: z.string().min(1, '名前は必須です').max(100, '名前は100文字以内で入力してください'),
  email: z.string().email('有効なメールアドレスを入力してください'),
  age: z.number().int().min(0).max(150).optional(),
  role: z.enum(['admin', 'user', 'guest']).default('user'),
  createdAt: z.date().default(() => new Date()),
  metadata: z.record(z.string(), z.unknown()).optional(),
})

// スキーマから型を自動推論(型定義の二重管理が不要)
type User = z.infer<typeof UserSchema>

// バリデーション実行
const result = UserSchema.safeParse({
  id: '123e4567-e89b-12d3-a456-426614174000',
  name: '丸山侑太',
  email: 'user@example.com',
  role: 'admin',
})

if (result.success) {
  console.log(result.data) // 型安全なデータ(User型)
} else {
  console.error(result.error.errors) // バリデーションエラー配列
}

// 変換・前処理(.transform・.preprocess)
const NormalizedEmailSchema = z.string()
  .email()
  .toLowerCase()               // メールアドレスを小文字に変換
  .trim()                       // 前後の空白を削除

const CoercedDateSchema = z.coerce.date()  // 文字列→Date型に自動変換

// 条件付きバリデーション(.superRefine・.refine)
const PasswordSchema = z.object({
  password: z.string().min(8),
  confirmPassword: z.string(),
}).refine(data => data.password === data.confirmPassword, {
  message: 'パスワードが一致しません',
  path: ['confirmPassword'],  // エラーをconfirmPasswordフィールドに紐付け
})

// discriminated union(タグ付きユニオン)
const PaymentSchema = z.discriminatedUnion('method', [
  z.object({
    method: z.literal('credit_card'),
    cardNumber: z.string().regex(/^\d{16}$/, '16桁のカード番号を入力'),
    expiryDate: z.string().regex(/^\d{2}\/\d{2}$/, 'MM/YY形式で入力'),
  }),
  z.object({
    method: z.literal('bank_transfer'),
    bankCode: z.string().regex(/^\d{4}$/),
    accountNumber: z.string().regex(/^\d{7,8}$/),
  }),
])
// Zod + React Hook Form の統合
import { useForm } from 'react-hook-form'
import { zodResolver } from '@hookform/resolvers/zod'
import { z } from 'zod'

const ContactSchema = z.object({
  name: z.string().min(1, '名前を入力してください'),
  email: z.string().email('有効なメールアドレスを入力してください'),
  message: z.string().min(10, '10文字以上入力してください').max(2000),
  subscribe: z.boolean().default(false),
})

type ContactForm = z.infer<typeof ContactSchema>

export function ContactForm() {
  const { register, handleSubmit, formState: { errors, isSubmitting } } = useForm<ContactForm>({
    resolver: zodResolver(ContactSchema),
    defaultValues: { subscribe: false },
  })

  const onSubmit = async (data: ContactForm) => {
    await fetch('/api/contact', {
      method: 'POST',
      body: JSON.stringify(data),
    })
  }

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input {...register('name')} />
      {errors.name && <p>{errors.name.message}</p>}
      <input {...register('email')} />
      {errors.email && <p>{errors.email.message}</p>}
      <button type="submit" disabled={isSubmitting}>送信</button>
    </form>
  )
}
// Next.js App Router: Server ActionでZodバリデーション
'use server'
import { z } from 'zod'

const CreateArticleSchema = z.object({
  title: z.string().min(1).max(200),
  content: z.string().min(100),
  category: z.enum(['tech', 'design', 'business']),
  tags: z.array(z.string()).max(10),
})

export async function createArticle(formData: FormData) {
  const raw = Object.fromEntries(formData)
  const parsed = CreateArticleSchema.safeParse({
    ...raw,
    tags: formData.getAll('tags'),
  })

  if (!parsed.success) {
    return { error: parsed.error.flatten().fieldErrors }
  }

  // バリデーション済みデータを使ってDB保存
  const article = await db.article.create({ data: parsed.data })
  return { success: true, id: article.id }
}

Valibot

2023年公開、TypeScript製のOSSです。GitHubスター7k+。Zodより最大98%小さいバンドルサイズ(8KB→1.4KB)を実現するモジュラー設計のバリデーションライブラリです。Tree-shakingが完全に機能してインポートした関数のみがバンドルに含まれます。

// Valibot: 超軽量・モジュラーバリデーション
import * as v from 'valibot'

// Zodと同等の機能をより小さなバンドルサイズで実現
const UserSchema = 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.integer(), v.minValue(0))),
  role: v.picklist(['admin', 'user', 'guest']),
})

type User = v.InferOutput<typeof UserSchema>

const result = v.safeParse(UserSchema, { name: '太郎', email: 'test@example.com', role: 'user' })
if (result.success) {
  console.log(result.output)
}

Yup

2017年公開、JavaScript製のOSSです。GitHubスター22k+。Formik・React Hook Form両対応の老舗バリデーションライブラリで、.when()による条件付きバリデーションや非同期バリデーション(API呼び出し)が直感的に書けます。

// Yup: 条件付き・非同期バリデーション
import * as yup from 'yup'

const RegisterSchema = yup.object({
  username: yup.string()
    .required('ユーザー名は必須')
    .min(3).max(20)
    .test('unique', 'このユーザー名は使用済みです', async (value) => {
      if (!value) return false
      const res = await fetch(`/api/check-username?username=${value}`)
      const { available } = await res.json()
      return available
    }),
  isCompany: yup.boolean().default(false),
  companyName: yup.string().when('isCompany', {
    is: true,
    then: schema => schema.required('法人名は必須です').min(1),
    otherwise: schema => schema.optional(),
  }),
})

機能比較表

比較項目ZodValibotYup
バンドルサイズ8KB✅ 1.4KB12KB
TypeScript推論
非同期バリデーション
tRPC統合
GitHub Stars35k+7k+22k+

バリデーションライブラリはDevOpsカテゴリ/categories/devopsのCI/CDパイプラインでAPIスキーマ変更を自動検知して型安全を保証するテストと組み合わせます。LLM Toolsカテゴリ/categories/llm-toolsのAI生成コンテンツ(構造化出力)をZodスキーマで検証してLLMの出力形式を型安全に保証するパターン(Structured Output)に広く活用されています。

FAQ

Q. ZodでLLMの構造化出力(Structured Output)を型安全に扱うには?

A. Anthropic/OpenAI の Structured Output 機能とZodを組み合わせます。Anthropic APIでのZod統合(zod-to-json-schema使用): ①const schema = zodToJsonSchema(ProductSchema)でJSON Schemaに変換②toolsパラメーターにtool定義として渡す③返却されたtool_use結果をZodで再バリデーション。OpenAI Structured Output(GPT-4o): openai.beta.chat.completions.parse()response_format: {type: 'json_schema', json_schema: zodToJsonSchema(schema)}を設定→message.parsedが自動的に型安全なオブジェクトとして返却されます。LangChain: withStructuredOutput(schema)メソッドがzodスキーマを直接受け付けます。

Q. ZodスキーマをPrismaスキーマと同期させるには?

A. zod-prisma-typesまたはprisma-zod-generatorでPrismaスキーマからZodスキーマを自動生成します。①schema.prismagenerator zod {provider = "zod-prisma-types"}を追加②npx prisma generateでPrismaスキーマの全モデルに対応するZodスキーマがprisma/generated/zod/に自動生成③生成されたスキーマをAPIのバリデーションやフォームにインポートして使用。利点: PrismaモデルとZodスキーマが常に同期されるため、スキーマ変更時の手動更新ミスが発生しない。tRPCとの組み合わせ: tRPCのinput/outputにZodスキーマを指定してフロントエンドへの型の自動伝播を実現します。

Q. Zodでカスタムバリデーションルールを作成するには?

A. .refine()(フィールドレベル)または.superRefine()(複数フィールド横断)でカスタムロジックを追加します。.refine(): z.string().refine(val => !blockedDomains.includes(val.split('@')[1]), {message: 'このドメインは使用できません'}).superRefine(): 複数エラーを返せる高度なバリデーション。カスタムエラーマップ: z.setErrorMap((issue, ctx) => {if (issue.code === z.ZodIssueCode.too_small) return {message: 最小${issue.minimum}文字で入力してください}; return {message: ctx.defaultError}})でプロジェクト全体のエラーメッセージを一元管理できます。

Q. バンドルサイズが重要な場合はValibotとZodどちらを選ぶべきですか?

A. バンドルサイズを最優先するエッジ環境・Client Componentが多いSPAはValibotエコシステム統合とtRPCが重要なフルスタックはZodが向いています。サイズ比較: Zodは8KB(minified+gzipped)、Valibotは1.4KB(使用関数のみTree-shaking後)。実際の影響: Next.js App RouterではServer Componentでのみ使う場合はバンドルサイズが影響しないため、Server-sideではZodを使ってもクライアントバンドルは増えません。Edge Runtime(Cloudflare Workers・Vercel Edge Functions): バンドルサイズ制限(1MB以下)が厳しいためValibotが有利。API互換性: ValibotはZodと似たAPIで移行コストが低く、@valibot/zod-compatで段階的移行が可能です。

まとめ

ユースケース推奨ツール
TypeScript・tRPC・最大エコシステムZod
Edge環境・バンドルサイズ最小化Valibot
Formik連携・非同期バリデーションYup

関連外部リソース

他の記事も読む

Let's Build Together

OSS導入、自社だけで悩まない。

ツール選定から構築・運用・AI活用まで、オープンソースラボ運営元のClasslessが伴走します。初回のご相談は無料です。