yup の使い方

Dead simple Object schema validation

v1.7.110.0M/週MITバリデーション
AI生成コンテンツ

この記事はAIによって生成されました。内容の正確性は保証されません。最新の情報は公式ドキュメントをご確認ください。

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(),           // 追加
});

類似パッケージとの比較

特徴YupZodJoiValibot
バンドルサイズ~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}は正

比較記事