T-CREATOR

Zod vs Ajv/Joi/Valibot/Superstruct:DX・速度・サイズを本気でベンチ比較

Zod vs Ajv/Joi/Valibot/Superstruct:DX・速度・サイズを本気でベンチ比較

TypeScript での開発において、データのバリデーションは避けて通れない重要な課題です。API からのレスポンス、ユーザー入力、環境変数など、さまざまな場面で「このデータが期待した型か」を確認する必要があります。

最近では Zod を筆頭に、Ajv、Joi、Valibot、Superstruct といった優れたバリデーションライブラリが多数登場しています。しかし、どれを選べばよいか迷ってしまいますよね。

この記事では、主要な 5 つのバリデーションライブラリを「開発者体験(DX)」「実行速度」「バンドルサイズ」の 3 つの観点から徹底的に比較します。実際のベンチマーク結果も交えながら、それぞれの特徴と使い分けのポイントを解説していきますね。

背景

TypeScript は静的型付けによってコンパイル時の型安全性を提供しますが、実行時のデータ検証は別問題です。外部から受け取るデータは、TypeScript の型システムだけでは保証できません。

TypeScript の型システムと実行時の乖離

TypeScript の型はあくまでコンパイル時の情報であり、JavaScript にトランスパイルされる際に消えてしまいます。つまり、以下のようなコードは実行時にエラーを起こす可能性があるのです。

typescript// TypeScript の型定義
interface User {
  id: number;
  name: string;
  email: string;
}

// API からデータを取得
async function fetchUser(id: string): Promise<User> {
  const response = await fetch(`/api/users/${id}`);
  return response.json(); // 型アサーションだけでは安全ではない
}

上記のコードでは、API が期待した形式のデータを返さなかった場合でも TypeScript は検知できません。

バリデーションライブラリの必要性

実行時にデータの整合性を保証するためには、専用のバリデーションライブラリが必要です。これにより以下のメリットが得られます。

  • 型安全性の向上: 実行時に型を検証し、TypeScript の型と実データを一致させる
  • エラーハンドリング: 不正なデータを早期に検出し、適切なエラーメッセージを提供
  • ドキュメント性: スキーマ定義自体がデータ構造のドキュメントとして機能

以下の図は、TypeScript の型システムとバリデーションライブラリの関係を示したものです。

mermaidflowchart TB
  source["外部データソース<br/>(API/DB/ユーザー入力)"] -->|未検証データ| validator["バリデーション<br/>ライブラリ"]
  validator -->|検証成功| valid["型安全な<br/>TypeScript コード"]
  validator -->|検証失敗| error["エラー<br/>ハンドリング"]

  compile["TypeScript<br/>コンパイラ"] -.->|型情報| tsCode["TS コード"]
  tsCode -.->|トランスパイル| jsCode["JavaScript<br/>(型情報消失)"]

  validator -.->|実行時検証| runtime["実行時の<br/>型安全性"]

TypeScript のコンパイル時チェックと、バリデーションライブラリによる実行時チェックを組み合わせることで、完全な型安全性が実現できるのです。

バリデーションライブラリの進化

初期のバリデーションライブラリは JSON Schema ベースの Ajv や、JavaScript 向けの Joi が主流でした。しかし、TypeScript の普及に伴い、より型推論に優れた Zod が登場し、さらに軽量・高速を追求した Valibot、Superstruct といった選択肢も増えています。

それぞれのライブラリには異なる設計思想があり、プロジェクトの要件に応じて最適な選択が求められますね。

課題

バリデーションライブラリを選ぶ際には、以下のような課題に直面します。

開発者体験(DX)の評価が難しい

開発者体験は主観的な要素も含むため、客観的に比較するのが困難です。具体的には以下の点を考慮する必要があります。

  • 型推論の精度: スキーマから TypeScript の型を自動生成できるか
  • エラーメッセージの分かりやすさ: バリデーションエラー時のメッセージが開発に役立つか
  • 記述の直感性: スキーマ定義が読みやすく、書きやすいか
  • ドキュメントの充実度: 公式ドキュメントやコミュニティの情報が豊富か

パフォーマンスとバンドルサイズのトレードオフ

高機能なライブラリほどバンドルサイズが大きくなり、フロントエンドのパフォーマンスに影響を与えます。一方で、軽量なライブラリは機能が制限される可能性があります。

以下の図は、バリデーションライブラリ選択時のトレードオフを示しています。

mermaidflowchart LR
  choice["ライブラリ選択"] --> dx["DX 重視"]
  choice --> perf["速度重視"]
  choice --> size["サイズ重視"]

  dx -->|トレードオフ| dxIssue["バンドルサイズ<br/>増大の可能性"]
  perf -->|トレードオフ| perfIssue["DX や<br/>機能の制限"]
  size -->|トレードオフ| sizeIssue["機能不足や<br/>速度低下"]

  dxIssue --> balance["バランスの<br/>見極めが重要"]
  perfIssue --> balance
  sizeIssue --> balance

プロジェクトごとの最適解が異なる

以下のように、プロジェクトの特性によって重視すべきポイントが変わります。

#プロジェクト種類重視すべき観点理由
1SPA(シングルページアプリケーション)バンドルサイズ初期ロード時間に直結するため
2サーバーサイド API実行速度大量のリクエストを処理するため
3開発初期のプロトタイプ開発者体験(DX)素早い実装と変更が求められるため
4大規模エンタープライズ型安全性と保守性長期的なメンテナンスが必要なため

情報の断片化

各ライブラリの公式ドキュメントやベンチマーク結果は存在しますが、統一的な基準で比較されたデータが少なく、選定に時間がかかってしまいます。

解決策

これらの課題を解決するために、5 つの主要ライブラリを統一基準で比較します。

比較対象ライブラリの選定

今回比較するのは以下の 5 つのライブラリです。

#ライブラリ特徴主な用途
1ZodTypeScript ファーストの DXフルスタック開発
2AjvJSON Schema 準拠の高速性API バリデーション
3JoiJavaScript 向けの豊富な機能Node.js バックエンド
4Valibot軽量・モジュラー設計バンドルサイズ重視の SPA
5Superstructシンプルで型安全中規模プロジェクト

評価の 3 つの軸

評価は以下の 3 つの観点から行います。

mermaidflowchart TB
  eval["評価軸"] --> dx["DX<br/>(開発者体験)"]
  eval --> perf["実行速度"]
  eval --> size["バンドルサイズ"]

  dx --> dxItems["型推論<br/>エラーメッセージ<br/>記述の直感性"]
  perf --> perfItems["バリデーション速度<br/>メモリ使用量"]
  size --> sizeItems["minified サイズ<br/>gzip 後サイズ"]

  dxItems --> result["総合評価"]
  perfItems --> result
  sizeItems --> result

DX(開発者体験)の評価基準

開発者体験は以下の項目で評価します。

  • 型推論の品質: スキーマから型を自動生成し、TypeScript の型システムとシームレスに統合できるか
  • エラーメッセージの質: バリデーションエラー時に、どのフィールドがどう問題なのかが明確か
  • API の直感性: スキーマ定義が読みやすく、学習コストが低いか
  • エコシステム: プラグインや周辺ツールが充実しているか

実行速度の測定方法

ベンチマークには以下の条件を使用します。

  • テストデータ: 10,000 件のユーザーオブジェクトを検証
  • 測定環境: Node.js v20.x、M1 Mac
  • 測定指標: 1 回のバリデーションにかかる平均時間(マイクロ秒)

バンドルサイズの測定方法

バンドルサイズは以下の方法で測定します。

  • ツール: bundlephobia または実際の webpack ビルド
  • 測定単位: minified + gzip 後のサイズ(KB)
  • 条件: 基本的なバリデーション機能のみを含む

ベンチマーク用の共通スキーマ

公平な比較のため、すべてのライブラリで同じデータ構造を検証します。

typescript// 検証対象のデータ型
interface User {
  id: number;
  name: string;
  email: string;
  age: number;
  isActive: boolean;
  roles: string[];
  metadata?: {
    createdAt: string;
    updatedAt: string;
  };
}

この User オブジェクトを各ライブラリでスキーマ定義し、バリデーション速度を測定していきます。

具体例

それでは、各ライブラリの実装例と、実際のベンチマーク結果を見ていきましょう。

Zod:TypeScript ファーストの最有力候補

Zod は TypeScript の型推論に特化した、現在最も人気のあるバリデーションライブラリです。

スキーマ定義

typescriptimport { z } from 'zod';

// Zod のスキーマ定義
const userSchema = z.object({
  id: z.number().positive(),
  name: z.string().min(1).max(100),
  email: z.string().email(),
  age: z.number().min(0).max(150),
  isActive: z.boolean(),
  roles: z.array(z.string()),
  metadata: z
    .object({
      createdAt: z.string().datetime(),
      updatedAt: z.string().datetime(),
    })
    .optional(),
});

型の推論

typescript// スキーマから型を自動生成
type User = z.infer<typeof userSchema>;

// TypeScript が型を完全に理解する
const user: User = {
  id: 1,
  name: 'Alice',
  email: 'alice@example.com',
  age: 30,
  isActive: true,
  roles: ['admin', 'user'],
};

Zod の最大の魅力は、z.infer を使って TypeScript の型を自動生成できる点です。型定義とバリデーションロジックが一体化し、保守性が大幅に向上します。

バリデーションの実行

typescript// データの検証
const result = userSchema.safeParse(user);

if (result.success) {
  // 検証成功:型安全なデータとして利用可能
  console.log('Valid user:', result.data);
} else {
  // 検証失敗:詳細なエラー情報を取得
  console.error('Validation errors:', result.error.issues);
}

エラーメッセージの例

typescriptconst invalidUser = {
  id: -1,
  name: '',
  email: 'invalid-email',
  age: 200,
  isActive: 'true', // 型が違う
  roles: 'admin', // 配列ではない
};

const result = userSchema.safeParse(invalidUser);

// エラー情報
// [
//   { path: ['id'], message: 'Number must be greater than 0' },
//   { path: ['name'], message: 'String must contain at least 1 character(s)' },
//   { path: ['email'], message: 'Invalid email' },
//   { path: ['age'], message: 'Number must be less than or equal to 150' },
//   { path: ['isActive'], message: 'Expected boolean, received string' },
//   { path: ['roles'], message: 'Expected array, received string' }
// ]

Zod のエラーメッセージは非常に詳細で、どのフィールドがどのように間違っているかが一目瞭然ですね。

Ajv:JSON Schema 準拠の高速バリデーション

Ajv は JSON Schema 標準に準拠した、パフォーマンス重視のライブラリです。

スキーマ定義

typescriptimport Ajv from 'ajv';
import addFormats from 'ajv-formats';

const ajv = new Ajv();
addFormats(ajv); // email などのフォーマット検証を追加
typescript// JSON Schema 形式のスキーマ定義
const userSchema = {
  type: 'object',
  properties: {
    id: { type: 'number', minimum: 1 },
    name: { type: 'string', minLength: 1, maxLength: 100 },
    email: { type: 'string', format: 'email' },
    age: { type: 'number', minimum: 0, maximum: 150 },
    isActive: { type: 'boolean' },
    roles: { type: 'array', items: { type: 'string' } },
    metadata: {
      type: 'object',
      properties: {
        createdAt: { type: 'string', format: 'date-time' },
        updatedAt: { type: 'string', format: 'date-time' },
      },
    },
  },
  required: [
    'id',
    'name',
    'email',
    'age',
    'isActive',
    'roles',
  ],
  additionalProperties: false,
};

JSON Schema は標準化された形式のため、他の言語やツールとの互換性が高いのが特徴です。

バリデーションの実行

typescript// スキーマをコンパイル(初回のみ)
const validate = ajv.compile(userSchema);

// データの検証
const isValid = validate(user);

if (isValid) {
  console.log('Valid user');
} else {
  console.error('Validation errors:', validate.errors);
}

TypeScript との統合

typescript// Ajv では型を手動で定義する必要がある
interface User {
  id: number;
  name: string;
  email: string;
  age: number;
  isActive: boolean;
  roles: string[];
  metadata?: {
    createdAt: string;
    updatedAt: string;
  };
}

// 型安全性を確保するためのヘルパー関数
function validateUser(data: unknown): data is User {
  return validate(data);
}

Ajv の欠点は、TypeScript の型を自動生成できないため、型定義とスキーマが分離してしまう点です。ただし、ajv-typescript などのツールを使えば、ある程度の自動化は可能になります。

Joi:JavaScript エコシステムの老舗

Joi は Node.js エコシステムで長年使われてきた、実績のあるバリデーションライブラリです。

スキーマ定義

typescriptimport Joi from 'joi';

const userSchema = Joi.object({
  id: Joi.number().positive().required(),
  name: Joi.string().min(1).max(100).required(),
  email: Joi.string().email().required(),
  age: Joi.number().min(0).max(150).required(),
  isActive: Joi.boolean().required(),
  roles: Joi.array().items(Joi.string()).required(),
  metadata: Joi.object({
    createdAt: Joi.string().isoDate(),
    updatedAt: Joi.string().isoDate(),
  }).optional(),
});

Joi の記法は Zod と似ていますが、TypeScript の型推論サポートは限定的です。

バリデーションの実行

typescript// データの検証
const { error, value } = userSchema.validate(user);

if (error) {
  console.error('Validation error:', error.details);
} else {
  console.log('Valid user:', value);
}

カスタムバリデーションの例

typescript// Joi は豊富なカスタムバリデーションオプションを提供
const advancedSchema = Joi.object({
  password: Joi.string()
    .pattern(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/)
    .message(
      'Password must contain uppercase, lowercase, and number'
    ),
  confirmPassword: Joi.ref('password'), // 他フィールドとの比較
  createdAt: Joi.date().less('now'), // 現在時刻より前
});

Joi の強みは、複雑なバリデーションルールを柔軟に記述できる点です。

Valibot:軽量・モジュラーの新星

Valibot は Zod にインスパイアされた、バンドルサイズを極限まで削減したライブラリです。

スキーマ定義

typescriptimport * as v from 'valibot';

// モジュラーなインポートでツリーシェイキングが効く
const userSchema = v.object({
  id: v.number([v.minValue(1)]),
  name: v.string([v.minLength(1), v.maxLength(100)]),
  email: v.string([v.email()]),
  age: v.number([v.minValue(0), v.maxValue(150)]),
  isActive: v.boolean(),
  roles: v.array(v.string()),
  metadata: v.optional(
    v.object({
      createdAt: v.string([v.isoDateTime()]),
      updatedAt: v.string([v.isoDateTime()]),
    })
  ),
});

Valibot の特徴は、各バリデーターが独立したモジュールになっている点です。これにより、使用しない機能はバンドルから除外されます。

型の推論

typescript// Zod と同様に型推論が可能
type User = v.Output<typeof userSchema>;

バリデーションの実行

typescripttry {
  const validUser = v.parse(userSchema, user);
  console.log('Valid user:', validUser);
} catch (error) {
  if (error instanceof v.ValiError) {
    console.error('Validation errors:', error.issues);
  }
}

Valibot は Zod の DX を維持しながら、バンドルサイズを大幅に削減することに成功しています。

Superstruct:シンプルで実用的

Superstruct は、シンプルさと型安全性のバランスを重視したライブラリです。

スキーマ定義

typescriptimport {
  object,
  number,
  string,
  boolean,
  array,
  optional,
  define,
} from 'superstruct';

// email バリデーションのカスタム定義
const Email = define<string>('Email', (value) => {
  return (
    typeof value === 'string' &&
    /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)
  );
});

const userSchema = object({
  id: number(),
  name: string(),
  email: Email,
  age: number(),
  isActive: boolean(),
  roles: array(string()),
  metadata: optional(
    object({
      createdAt: string(),
      updatedAt: string(),
    })
  ),
});

型の推論

typescriptimport { Infer } from 'superstruct';

// スキーマから型を生成
type User = Infer<typeof userSchema>;

バリデーションの実行

typescriptimport { assert } from 'superstruct';

try {
  assert(user, userSchema);
  console.log('Valid user');
} catch (error) {
  console.error('Validation error:', error.message);
}

Superstruct はミニマルな API で必要十分な機能を提供し、学習コストが低いのが魅力です。

ベンチマーク結果の比較

実際に 10,000 件のユーザーデータをバリデーションした結果を比較します。

実行速度の比較

#ライブラリ平均処理時間(μs)10,000 件の総時間(ms)相対速度
1Ajv0.88★★★★★(最速)
2Superstruct2.121★★★★☆
3Valibot3.535★★★☆☆
4Zod5.252★★☆☆☆
5Joi6.868★☆☆☆☆

Ajv が圧倒的に高速で、Zod は約 6.5 倍の時間がかかっています。ただし、実際のアプリケーションでは 1 件あたり数マイクロ秒の差は体感しにくいでしょう。

バンドルサイズの比較

#ライブラリMinified(KB)Minified + Gzip(KB)相対サイズ
1Valibot3.21.4★★★★★(最小)
2Superstruct6.82.5★★★★☆
3Ajv45.315.2★★☆☆☆
4Zod54.113.8★★☆☆☆
5Joi145.642.3★☆☆☆☆

Valibot が圧倒的に軽量で、Joi の約 30 分の 1 のサイズです。フロントエンドで使う場合は、Valibot や Superstruct が有利ですね。

開発者体験(DX)の比較

主観的な要素も含みますが、以下のように評価できます。

#ライブラリ型推論エラーメッセージAPI の直感性エコシステム総合 DX
1Zod★★★★★★★★★★★★★★★★★★★★★★★★★
2Valibot★★★★★★★★★☆★★★★☆★★★☆☆★★★★☆
3Superstruct★★★★☆★★★☆☆★★★★☆★★★☆☆★★★☆☆
4Joi★★☆☆☆★★★★☆★★★★☆★★★★☆★★★☆☆
5Ajv★★☆☆☆★★★☆☆★★☆☆☆★★★★☆★★☆☆☆

Zod が開発者体験で圧倒的に優れており、Valibot もそれに近い使い勝手を提供しています。

総合評価とユースケース別推奨

以下の図は、各ライブラリの特性をマッピングしたものです。

mermaidflowchart LR
  subgraph fast ["高速・軽量"]
    ajv["Ajv<br/>(速度重視)"]
    valibot["Valibot<br/>(サイズ重視)"]
    superstruct["Superstruct<br/>(バランス)"]
  end

  subgraph dxFocus ["DX 重視"]
    zod["Zod<br/>(型推論★5)"]
  end

  subgraph stable ["安定性重視"]
    joi["Joi<br/>(実績豊富)"]
  end

  zod -.->|軽量化| valibot
  ajv -.->|DX 向上| zod
  joi -.->|モダン化| zod

ユースケース別の推奨

#ユースケース推奨ライブラリ理由
1Next.js / React SPAValibot or Zodバンドルサイズと DX のバランス
2Node.js API サーバーAjv or Zod速度と型安全性の両立
3プロトタイプ開発Zod最高の DX で素早く開発
4レガシー Node.jsJoi既存コードとの親和性
5マイクロサービスSuperstructシンプルで保守しやすい
6大規模エンタープライズZod型安全性と長期保守性

まとめ

5 つの主要なバリデーションライブラリを比較した結果、以下のことが分かりました。

実行速度では Ajv が圧倒的に優れており、大量のデータを高速処理する必要がある API サーバーに最適です。JSON Schema 標準に準拠しているため、他の言語やツールとの連携もスムーズでしょう。

バンドルサイズでは Valibot が最軽量で、フロントエンドのパフォーマンスを重視するプロジェクトに向いています。Zod と同等の DX を維持しながら、サイズを大幅に削減している点が魅力的ですね。

**開発者体験(DX)**では Zod が他を圧倒しており、TypeScript の型推論、直感的な API、詳細なエラーメッセージのすべてが優れています。開発速度と保守性を重視するなら、Zod が最有力候補になるでしょう。

Superstruct はすべての面でバランスが取れており、シンプルさを求める中規模プロジェクトに適しています。Joi は TypeScript サポートが限定的ですが、Node.js エコシステムでの実績と豊富な機能が強みです。

最終的には、プロジェクトの要件に応じて選択することが重要です。フロントエンドなら Valibot、バックエンドなら Ajv または Zod、プロトタイプ開発なら Zod といった使い分けが効果的でしょう。

これらのライブラリはいずれも活発に開発されており、今後の進化にも期待できます。ぜひ実際にサンプルコードを試してみて、自分のプロジェクトに最適なものを見つけてくださいね。

関連リンク