joi vs yup vs superstruct vs io-ts
フロントエンドにおける実行時バリデーションと型安全のためのライブラリ比較
joiyupsuperstructio-ts類似パッケージ:

フロントエンドにおける実行時バリデーションと型安全のためのライブラリ比較

io-tsjoisuperstructyup はいずれも JavaScript/TypeScript アプリケーションにおける実行時バリデーション(runtime validation)を提供するライブラリです。これらは、API レスポンス、ユーザー入力、設定ファイルなどの外部データが期待通りの構造を持っているかを検証し、不正なデータによるバグやセキュリティリスクを防ぐために使われます。同時に、TypeScript との連携を通じて静的型安全性を高める役割も果たします。

npmのダウンロードトレンド

3 年

GitHub Starsランキング

統計詳細

パッケージ
ダウンロード数
Stars
サイズ
Issues
公開日時
ライセンス
joi17,729,71721,202557 kB1903ヶ月前BSD-3-Clause
yup10,021,05323,690270 kB2405ヶ月前MIT
superstruct3,982,9977,155182 kB982年前MIT
io-ts2,355,9636,824460 kB1621年前MIT

io-ts vs Joi vs Superstruct vs Yup:フロントエンド開発におけるバリデーションと型安全の設計比較

JavaScript は動的型付け言語であり、API やユーザー入力から受け取るデータの形状が予期せず変化することがよくあります。このため、多くのプロジェクトでは「実行時バリデーション(runtime validation)」と「静的型安全性(static type safety)」を組み合わせたアプローチが必要です。io-tsjoisuperstructyup はいずれもその目的に使われますが、設計思想やユースケース、TypeScript との連携方法に大きな違いがあります。

🧪 基本的な使い方:スキーマ定義とバリデーション

io-ts

io-tsTypeScript の型システムと完全に統合することを目的としており、実行時チェック用の「コーデック(codec)」を定義すると、そこから TypeScript の型が自動生成されます。これは「1つの定義で両方を満たす」という設計哲学に基づいています。

import * as t from 'io-ts';

const User = t.type({
  id: t.number,
  name: t.string,
});

// 実行時バリデーション
type UserType = t.TypeOf<typeof User>;

const result = User.decode({ id: 1, name: 'Alice' });
if (result._tag === 'Right') {
  const user: UserType = result.right; // 型安全!
}

joi

joi は元々 Hapi.js のバリデーションライブラリとして生まれ、柔軟かつ高機能なルール定義が特徴です。ただし、TypeScript との統合は公式サポートされておらず、型情報は手動で書く必要があります。

const Joi = require('joi');

const userSchema = Joi.object({
  id: Joi.number().required(),
  name: Joi.string().required(),
});

const { error, value } = userSchema.validate({ id: 1, name: 'Alice' });
if (!error) {
  // value を使う(TypeScript では any または独自型が必要)
}

superstruct

superstructシンプルで高速な実行時バリデーションを目指しており、スキーマ定義が非常に簡潔です。TypeScript との連携は可能ですが、io-ts のような自動型推論は提供していません。

import { object, number, string, assert } from 'superstruct';

const User = object({
  id: number(),
  name: string(),
});

try {
  assert({ id: 1, name: 'Alice' }, User);
  // バリデーション成功 → 以降のコードで安全に使用可能
} catch (e) {
  // エラー処理
}

yup

yup直感的でチェーン可能な API を持ち、フォームバリデーションなどインタラクティブな UI での利用に適しています。TypeScript との連携は部分的に可能ですが、完全な型推論には限界があります。

import * as yup from 'yup';

const userSchema = yup.object({
  id: yup.number().required(),
  name: yup.string().required(),
});

try {
  const user = await userSchema.validate({ id: 1, name: 'Alice' });
  // user を使う(型は infer で取得可能だが、完全ではない)
} catch (err) {
  // エラー処理
}

🔗 TypeScript との統合度:型安全の確保方法

io-ts:型とバリデーションの完全同期

io-ts は、スキーマ定義から TypeScript の型を自動生成する仕組みを持ちます。これにより、「実行時にもコンパイル時にも正しい型」を保証できます。

const Post = t.type({
  title: t.string,
  published: t.boolean,
});

type PostType = t.TypeOf<typeof Post>; // { title: string; published: boolean }

このアプローチは、API レスポンスや外部データソースの型を厳密に管理したい場合に非常に強力です。

joi:型は別途定義が必要

joi 自体は TypeScript の型情報を生成しません。そのため、以下のように手動で型を定義する必要があります。

type User = {
  id: number;
  name: string;
};

const schema = Joi.object({
  id: Joi.number().required(),
  name: Joi.string().required(),
});

型とスキーマの乖離リスクがあるため、保守コストが増える可能性があります。

superstruct:型付きだが自動推論なし

superstruct は TypeScript で書かれており、型安全に使えるようになっていますが、スキーマから型を自動生成する機能はありません。

interface User {
  id: number;
  name: string;
}

const UserStruct = object({
  id: number(),
  name: string(),
});

型と構造体を別々に定義する必要があり、重複が発生します。

yupinferType による限定的推論

yupyup.InferType<typeof schema> を使って型を推論できますが、すべてのケースで正確に動作するわけではありません(特にカスタムテストや条件付きバリデーションでは)。

const schema = yup.object({
  email: yup.string().email(),
});

type SchemaType = yup.InferType<typeof schema>; // { email?: string }

オプショナルフィールドや複雑な条件では、期待通りの型にならないことがあります。

⚙️ バリデーションの柔軟性と拡張性

joi:最も豊富なビルトインルール

joi はメール、URL、日付、範囲、パターンマッチなど、非常に多くのバリデーションルールを標準で提供しています。

const schema = Joi.object({
  email: Joi.string().email(),
  age: Joi.number().min(18).max(100),
  password: Joi.string().pattern(/^[a-zA-Z0-9]{8,}$/),
});

yup:チェーン式で直感的

yup も同様に多くのルールを持ち、メソッドチェーンで記述できるため読みやすいです。

const schema = yup.object({
  email: yup.string().email().required(),
  age: yup.number().min(18).max(100).required(),
});

superstruct:シンプルだが拡張可能

superstruct は基本的な型(string, number, boolean, array, object など)に加え、defineunionintersection などでカスタムロジックを追加できます。

const email = define<string>('email', (value) => typeof value === 'string' && value.includes('@'));
const User = object({ email });

io-ts:合成と再利用に強い

io-ts は関数型プログラミングの原則に基づき、intersectionunionpartialrecord などの組み合わせで複雑なスキーマを構築できます。

const OptionalUser = t.partial({
  name: t.string,
});
const FullUser = t.intersection([User, OptionalUser]);

📦 フロントエンドでの実用性:バンドルサイズと DX

  • io-ts:TypeScript プロジェクトで型安全を最優先するなら最適。ただし、学習曲線や冗長さがある。
  • joi:Node.js 向けに最適化されており、フロントエンドではやや重たい印象。ブラウザ向けには非推奨とされている(公式ドキュメントに記載あり)。
  • superstruct:軽量でシンプル。React や Vue などのフロントエンドフレームワークと相性が良い。
  • yup:フォームライブラリ(例:Formik)との統合が広く使われており、UI との連携に強い。

💡 注:joi は npm ページおよび GitHub リポジトリで「ブラウザ環境での使用は推奨されない」と明記されています。フロントエンドプロジェクトでは代替手段を検討すべきです。

🔄 エラーハンドリングとフィードバック

io-ts:詳細なエラー構造

io-ts のエラーは階層的で、どのフィールドで何が失敗したかを正確にトレースできます。

const result = User.decode({ id: 'not a number', name: 123 });
// result.left には Path[] と Value のリストが含まれる

yup:ユーザーフレンドリーなエラーメッセージ

yup はエラーメッセージをカスタマイズしやすく、フォーム表示に直接使える文字列を返します。

try {
  await schema.validate(data, { abortEarly: false });
} catch (err) {
  console.log(err.errors); // ['email must be a valid email', ...]
}

superstruct:例外ベースのシンプルなハンドリング

assert は不正なデータで例外を投げるため、try/catch で処理します。エラー内容は比較的シンプルです。

joi:包括的なエラーオブジェクト

joierror.details に各フィールドのエラー理由を配列で返し、国際化やカスタムメッセージにも対応しています。

📌 まとめ:各ライブラリの強みと選定基準

ライブラリ強み弱み推奨用途
io-tsTypeScript との完全統合、型安全記述が冗長、学習コスト高型安全性が最重要な大規模アプリ、API レスポンスの厳密な検証
joi高機能、柔軟なルールブラウザ非推奨、バンドルが重いNode.js バックエンド、サーバーサイドバリデーション
superstruct軽量、シンプル、高速型推論なし、機能が最小限小〜中規模フロントエンド、パフォーマンス重視のアプリ
yup直感的、フォーム連携が豊富型推論に限界、非同期バリデーションが複雑React/Vue フォーム、UI 寄りのバリデーション

💡 最終的なアドバイス

  • TypeScript プロジェクトで「型と実行時チェックを1つに統合したい」io-ts
  • フロントエンドで軽くてシンプルなバリデーションが欲しいsuperstruct
  • フォームバリデーションでユーザーフレンドリーなエラーメッセージが必要yup
  • Node.js サーバーで高機能なバリデーションが必要joi(ただしフロントエンドでは避ける)

これらの選択は、プロジェクトの規模、チームのスキルセット、そして「どこまで型安全を求めるか」によって変わります。一つの正解はありませんが、目的に合ったツールを選ぶことで、開発体験とアプリの信頼性を大きく向上させられます。

選び方: joi vs yup vs superstruct vs io-ts

  • joi:

    joi は高機能で柔軟なバリデーションルールを提供し、Node.js バックエンドでの利用に強く最適化されています。ただし、公式ドキュメントで「ブラウザ環境での使用は推奨されない」と明記されており、フロントエンドプロジェクトではバンドルサイズや互換性の観点から避けるべきです。サーバーサイドでのデータ検証や設定バリデーションに限定して使用してください。

  • yup:

    yup はチェーン可能な直感的な API と、フォームライブラリ(例:Formik)との高い親和性が特徴です。ユーザーフレンドリーなエラーメッセージを簡単に生成でき、インタラクティブな UI での入力検証に最適です。ただし、TypeScript の型推論は完全ではなく、複雑なスキーマでは手動調整が必要になることがあります。

  • superstruct:

    superstruct は軽量でシンプルな API を持ち、パフォーマンスと可読性を重視するフロントエンドプロジェクトに適しています。TypeScript との連携は可能ですが、型は手動で定義する必要があります。小〜中規模のアプリで、最小限の依存と高速なバリデーションを求める場合に最良の選択肢です。

  • io-ts:

    io-ts は、TypeScript の型システムと実行時バリデーションを完全に統合したいプロジェクトに最適です。スキーマ定義から自動的に TypeScript の型が生成されるため、API レスポンスや外部データの型を厳密に管理したい大規模アプリケーションに向いています。ただし、記述が冗長で学習コストが高いため、小規模プロジェクトや型安全性よりも開発速度を重視するケースではオーバーキルになる可能性があります。

joi のREADME

joi

The most powerful schema description language and data validator for JavaScript.

Installation

npm install joi

Visit the joi.dev Developer Portal for tutorials, documentation, and support

Useful resources