io-ts、joi、superstruct、yup はいずれも JavaScript/TypeScript アプリケーションにおける実行時バリデーション(runtime validation)を提供するライブラリです。これらは、API レスポンス、ユーザー入力、設定ファイルなどの外部データが期待通りの構造を持っているかを検証し、不正なデータによるバグやセキュリティリスクを防ぐために使われます。同時に、TypeScript との連携を通じて静的型安全性を高める役割も果たします。
JavaScript は動的型付け言語であり、API やユーザー入力から受け取るデータの形状が予期せず変化することがよくあります。このため、多くのプロジェクトでは「実行時バリデーション(runtime validation)」と「静的型安全性(static type safety)」を組み合わせたアプローチが必要です。io-ts、joi、superstruct、yup はいずれもその目的に使われますが、設計思想やユースケース、TypeScript との連携方法に大きな違いがあります。
io-tsio-ts は TypeScript の型システムと完全に統合することを目的としており、実行時チェック用の「コーデック(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; // 型安全!
}
joijoi は元々 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 または独自型が必要)
}
superstructsuperstruct は シンプルで高速な実行時バリデーションを目指しており、スキーマ定義が非常に簡潔です。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) {
// エラー処理
}
yupyup は 直感的でチェーン可能な 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) {
// エラー処理
}
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(),
});
型と構造体を別々に定義する必要があり、重複が発生します。
yup:inferType による限定的推論yup は yup.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 など)に加え、define や union、intersection などでカスタムロジックを追加できます。
const email = define<string>('email', (value) => typeof value === 'string' && value.includes('@'));
const User = object({ email });
io-ts:合成と再利用に強いio-ts は関数型プログラミングの原則に基づき、intersection、union、partial、record などの組み合わせで複雑なスキーマを構築できます。
const OptionalUser = t.partial({
name: t.string,
});
const FullUser = t.intersection([User, OptionalUser]);
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:包括的なエラーオブジェクトjoi は error.details に各フィールドのエラー理由を配列で返し、国際化やカスタムメッセージにも対応しています。
| ライブラリ | 強み | 弱み | 推奨用途 |
|---|---|---|---|
io-ts | TypeScript との完全統合、型安全 | 記述が冗長、学習コスト高 | 型安全性が最重要な大規模アプリ、API レスポンスの厳密な検証 |
joi | 高機能、柔軟なルール | ブラウザ非推奨、バンドルが重い | Node.js バックエンド、サーバーサイドバリデーション |
superstruct | 軽量、シンプル、高速 | 型推論なし、機能が最小限 | 小〜中規模フロントエンド、パフォーマンス重視のアプリ |
yup | 直感的、フォーム連携が豊富 | 型推論に限界、非同期バリデーションが複雑 | React/Vue フォーム、UI 寄りのバリデーション |
io-tssuperstructyupjoi(ただしフロントエンドでは避ける)これらの選択は、プロジェクトの規模、チームのスキルセット、そして「どこまで型安全を求めるか」によって変わります。一つの正解はありませんが、目的に合ったツールを選ぶことで、開発体験とアプリの信頼性を大きく向上させられます。
joi は高機能で柔軟なバリデーションルールを提供し、Node.js バックエンドでの利用に強く最適化されています。ただし、公式ドキュメントで「ブラウザ環境での使用は推奨されない」と明記されており、フロントエンドプロジェクトではバンドルサイズや互換性の観点から避けるべきです。サーバーサイドでのデータ検証や設定バリデーションに限定して使用してください。
yup はチェーン可能な直感的な API と、フォームライブラリ(例:Formik)との高い親和性が特徴です。ユーザーフレンドリーなエラーメッセージを簡単に生成でき、インタラクティブな UI での入力検証に最適です。ただし、TypeScript の型推論は完全ではなく、複雑なスキーマでは手動調整が必要になることがあります。
superstruct は軽量でシンプルな API を持ち、パフォーマンスと可読性を重視するフロントエンドプロジェクトに適しています。TypeScript との連携は可能ですが、型は手動で定義する必要があります。小〜中規模のアプリで、最小限の依存と高速なバリデーションを求める場合に最良の選択肢です。
io-ts は、TypeScript の型システムと実行時バリデーションを完全に統合したいプロジェクトに最適です。スキーマ定義から自動的に TypeScript の型が生成されるため、API レスポンスや外部データの型を厳密に管理したい大規模アプリケーションに向いています。ただし、記述が冗長で学習コストが高いため、小規模プロジェクトや型安全性よりも開発速度を重視するケースではオーバーキルになる可能性があります。
npm install joi