Yup の使い方完全ガイド — JavaScriptオブジェクトのバリデーションをシンプルに
一言でいうと
Yupは、ランタイムでの値のパース(型変換)とバリデーション(検証)を行うスキーマビルダーです。直感的なチェーンAPIでスキーマを定義し、フォーム入力やAPIレスポンスなどのデータを型安全に検証できます。
どんな時に使う?
- フォームバリデーション — React Hook FormやFormikと組み合わせて、ユーザー入力のバリデーションルールを宣言的に定義したい時
- APIリクエスト/レスポンスの検証 — 外部APIから受け取ったデータが期待する形状かどうかをランタイムで確認したい時
- 設定ファイルや環境変数の検証 — アプリケーション起動時に、設定値が正しい型・範囲に収まっているかチェックしたい時
インストール
# npm
npm install yup
# yarn
yarn add yup
# pnpm
pnpm add yup
TypeScriptの型定義は本体に同梱されているため、
@types/yupは不要です。
基本的な使い方
最も典型的なパターンは、オブジェクトスキーマを定義して validate で検証する流れです。
import { object, string, number, date, InferType } from 'yup';
// スキーマ定義
const userSchema = object({
name: string().required('名前は必須です'),
age: number().required('年齢は必須です').positive().integer(),
email: string().email('有効なメールアドレスを入力してください'),
website: string().url().nullable(),
createdOn: date().default(() => new Date()),
});
// スキーマから TypeScript の型を推論
type User = InferType<typeof userSchema>;
// {
// name: string;
// age: number;
// email?: string | undefined;
// website?: string | null | undefined;
// createdOn: Date;
// }
// バリデーション実行(非同期)
async function createUser(input: unknown) {
try {
const user = await userSchema.validate(input, { abortEarly: false });
console.log('Valid:', user);
return user;
} catch (err) {
if (err instanceof Error && err.name === 'ValidationError') {
console.error('Validation failed:', (err as any).errors);
}
throw err;
}
}
// 使用例
await createUser({
name: 'Taro',
age: 30,
email: 'taro@example.com',
website: null,
});
よく使うAPI
1. validate / validateSync — バリデーション実行
スキーマに対して値を検証します。validate は非同期、validateSync は同期版です。
import { string, ValidationError } from 'yup';
const emailSchema = string().email().required();
// 非同期バリデーション
try {
const validEmail = await emailSchema.validate('user@example.com');
console.log(validEmail); // 'user@example.com'
} catch (err) {
// ValidationError
}
// 同期バリデーション
try {
const validEmail = emailSchema.validateSync('invalid-email');
} catch (err) {
if (err instanceof ValidationError) {
console.log(err.message); // 'this must be a valid email'
}
}
// abortEarly: false で全エラーを収集
const userSchema = object({
name: string().required(),
email: string().email().required(),
});
try {
await userSchema.validate({}, { abortEarly: false });
} catch (err) {
if (err instanceof ValidationError) {
console.log(err.errors);
// ['name is a required field', 'email is a required field']
// inner にフィールドごとのエラー詳細が入る
err.inner.forEach((e) => {
console.log(`${e.path}: ${e.message}`);
});
}
}
2. cast — 型変換(パース)
バリデーションは行わず、値の型変換のみを実行します。文字列の "24" を数値の 24 に変換するなどの用途に便利です。
import { object, string, number, date } from 'yup';
const schema = object({
name: string(),
age: number(),
createdOn: date(),
});
const parsed = schema.cast({
name: 'jimmy',
age: '24', // 文字列 → 数値に変換
createdOn: '2024-01-15T00:00:00Z', // 文字列 → Date に変換
});
console.log(parsed);
// { name: 'jimmy', age: 24, createdOn: Date('2024-01-15T00:00:00Z') }
// stripUnknown: true で未定義フィールドを除去
const stripped = schema.cast(
{ name: 'jimmy', age: '24', extra: 'remove me' },
{ stripUnknown: true },
);
console.log(stripped);
// { name: 'jimmy', age: 24, createdOn: undefined } ← extra が除去される
3. when — 条件付きバリデーション
他のフィールドの値に応じてバリデーションルールを動的に切り替えられます。
import { object, string, number, boolean } from 'yup';
const orderSchema = object({
isDelivery: boolean().required(),
// isDelivery が true の場合のみ住所を必須にする
address: string().when('isDelivery', {
is: true,
then: (schema) => schema.required('配送先住所は必須です'),
otherwise: (schema) => schema.optional(),
}),
// 関数形式でも書ける
deliveryFee: number().when('isDelivery', ([isDelivery], schema) => {
return isDelivery
? schema.required().min(0)
: schema.strip(); // フィールドごと除去
}),
});
// ✅ 配送あり → address 必須
await orderSchema.validate({
isDelivery: true,
address: '東京都渋谷区...',
deliveryFee: 500,
});
// ❌ 配送ありなのに address がない → ValidationError
await orderSchema.validate({
isDelivery: true,
deliveryFee: 500,
});
4. test — カスタムバリデーション
組み込みバリデーションでは対応できない独自ルールを追加できます。
import { string, number, object, ref } from 'yup';
// シンプルなカスタムテスト
const passwordSchema = string()
.required()
.min(8, '8文字以上で入力してください')
.test(
'has-uppercase',
'大文字を1文字以上含めてください',
(value) => /[A-Z]/.test(value ?? ''),
)
.test(
'has-number',
'数字を1文字以上含めてください',
(value) => /\d/.test(value ?? ''),
);
// 非同期カスタムテスト(例: サーバー側でユニーク性チェック)
const usernameSchema = string()
.required()
.test(
'is-unique',
'このユーザー名は既に使用されています',
async (value) => {
if (!value) return true;
const response = await fetch(`/api/check-username?name=${value}`);
const { available } = await response.json();
return available;
},
);
// ref を使ったフィールド間の比較
const signupSchema = object({
password: string().required().min(8),
confirmPassword: string()
.required('確認用パスワードは必須です')
.oneOf([ref('password')], 'パスワードが一致しません'),
});
5. object の高度な操作 — shape, pick, omit, concat
オブジェクトスキーマの合成・分割が柔軟に行えます。
import { object, string, number } from 'yup';
// ベースとなるスキーマ
const baseUserSchema = object({
id: number().required(),
name: string().required(),
email: string().email().required(),
role: string().oneOf(['admin', 'user']).required(),
});
// pick: 必要なフィールドだけ抽出
const loginSchema = baseUserSchema.pick(['email']);
// { email: string }
// omit: 特定フィールドを除外
const createUserSchema = baseUserSchema.omit(['id']);
// { name: string, email: string, role: string }
// concat: スキーマを結合
const addressSchema = object({
address: string().required(),
city: string().required(),
});
const fullUserSchema = createUserSchema.concat(addressSchema);
// { name, email, role, address, city }
// shape: 既存スキーマにフィールドを追加・上書き
const adminSchema = baseUserSchema.shape({
role: string().oneOf(['admin']).required(), // 上書き
permissions: string().required(), // 追加
});
類似パッケージとの比較
| 特徴 | Yup | Zod | Joi | Valibot |
|---|---|---|---|---|
| バンドルサイズ | ~40KB | ~50KB | ~140KB | ~5KB(ツリーシェイク) |
| TypeScript推論 | ◎ | ◎(最も強力) | △(別途型定義が必要) | ◎ |
| 非同期バリデーション | ◎ | ◎ | ◎ | ◎ |
| ブラウザ対応 | ◎ | ◎ | △(Node.js向け) | ◎ |
| エコシステム連携 | ◎(Formik等) | ◎(急速に拡大) | ◎(Hapi等) | ○ |
| API スタイル | チェーン | チェーン | チェーン | 関数合成 |
| Standard Schema対応 | ◎ | ◎ | ✕ | ◎ |
選定の目安:
- Formikを使っている → Yup(公式推奨の組み合わせ)
- TypeScriptファーストで新規プロジェクト → Zod(型推論が最も強力)
- バンドルサイズを極限まで削りたい → Valibot
- 既にYupで動いている既存プロジェクト → そのままYupで問題なし
注意点・Tips
1. abortEarly: false を忘れずに
デフォルトでは最初のエラーが見つかった時点でバリデーションが中断されます。フォームで全フィールドのエラーを一括表示したい場合は、必ず abortEarly: false を指定してください。
await schema.validate(data, { abortEarly: false });
2. nullable() と optional() の違い
import { string } from 'yup';
// optional: undefined を許容(値が存在しなくてもOK)
string().optional(); // string | undefined
// nullable: null を許容
string().nullable(); // string | null
// required: undefined も空文字も拒否
string().required(); // string(空文字もNG)
// 両方許容したい場合
string().nullable().optional(); // string | null | undefined
3. strict モードで型変換を無効化
Yupはデフォルトで型変換(coercion)を行います。"24" が自動的に 24 になるのが困る場合は strict を使います。
const schema = number().required();
// デフォルト: "24" → 24 に変換してからバリデーション → ✅
await schema.validate('24');
// strict: 型変換しない → "24" は number ではない → ❌
await schema.validate('24', { strict: true });
4. React Hook Form との連携
import { useForm } from 'react-hook-form';
import { yupResolver } from '@hookform/resolvers/yup';
import { object, string } from 'yup';
const schema = object({
name: string().required('名前は必須です'),
email: string().email('有効なメールアドレスを入力してください').required(),
});
function MyForm() {
const { register, handleSubmit, formState: { errors } } = useForm({
resolver: yupResolver(schema),
});
return (
<form onSubmit={handleSubmit((data) => console.log(data))}>
<input {...register('name')} />
{errors.name && <p>{errors.name.message}</p>}
<input {...register('email')} />
{errors.email && <p>{errors.email.message}</p>}
<button type="submit">送信</button>
</form>
);
}
@hookform/resolversパッケージが別途必要です。
5. エラーメッセージの日本語化
import { setLocale } from 'yup';
setLocale({
mixed: {
required: '${path}は必須項目です',
oneOf: '${path}は次のいずれかでなければなりません: ${values}',
},
string: {
email: '${path}は有効なメールアドレス形式で入力してください',
min: '${path}は${min}文字以上で入力してください',
max: '${path}は${max}文字以下で入力してください',
},
number: {
min: '${path}は${min}以上の値を入力してください',
max: '${path}は${max}以下の値を入力してください',
positive: '${path}は正