Zodバリデーションの使い方完全ガイド|基礎から実践まで初心者向けに解説

kento_morota 18分で読めます

Zodとは?TypeScript向けバリデーションライブラリの基礎

Webアプリケーション開発において、ユーザー入力やAPIレスポンスなどの外部データを検証する仕組みは不可欠です。不正なデータがシステムに入り込むと、予期せぬエラーやセキュリティリスクにつながります。

Zod(ゾッド)は、TypeScript向けに設計された型安全なバリデーションライブラリです。データの検証ルールを定義すると同時に、TypeScriptの型情報も自動生成できる点が最大の特徴です。

従来のバリデーションでは「実行時のデータチェック」と「開発時の型チェック」を別々に管理する必要がありました。しかしZodを使えば、一つのスキーマ定義で両方を同時に実現できます。

import { z } from 'zod';

// スキーマ定義
const UserSchema = z.object({
  name: z.string(),
  age: z.number(),
  email: z.string().email(),
});

// 型を自動生成
type User = z.infer<typeof UserSchema>;

このコードだけで、バリデーションロジックと型情報の両方が得られます。型定義を二重に書く必要がなく、常に同期が保たれるため、型の不整合によるバグを防げます。

Zodが解決する開発課題

開発現場では以下のような課題に直面することがあります。

  • APIから受け取ったデータの型が実際には異なっていた
  • フォームの入力値が想定外の形式でエラーが発生した
  • バリデーションロジックとTypeScriptの型定義が二重管理になっている
  • ランタイムエラーが本番環境で発生してしまった

Zod導入のメリット:

  • 型安全性の向上:開発時とランタイムの両方で型をチェック
  • コードの簡潔性:直感的なAPIで少ないコード量で実装可能
  • エラーハンドリングの充実:詳細なエラー情報を取得でき、ユーザーフレンドリーなメッセージ表示が容易
  • 保守性の向上:スキーマが一元管理されるため、仕様変更時の修正箇所が明確

他のバリデーションライブラリとの比較

JavaScriptエコシステムには、Yup、Joi、class-validatorなど様々なバリデーションライブラリが存在します。

ライブラリ TypeScript対応 型推論 特徴
Zod ◎ネイティブ対応 ◎自動生成 TypeScript-first、型推論が強力
Yup ○型定義あり △限定的 React Hook Formとの実績が豊富
Joi ○型定義あり △限定的 Node.js環境で実績あり
class-validator ○デコレータ利用 ×別途定義 クラスベース、NestJSで標準採用

Zodの優位性:

  • z.inferによる強力な型推論
  • tree-shakingに対応した軽量性
  • メソッドチェーンで直感的にバリデーションを構築
  • 依存関係ゼロで導入が容易

Yupは実績がありますが型推論の面でZodに劣ります。Joiは機能豊富ですがバンドルサイズが大きく、フロントエンド開発には不向きです。

Zodを使うべきプロジェクト

以下のような特徴を持つプロジェクトで、Zodは特に効果を発揮します。

  • TypeScriptを採用している
  • フォームバリデーションが多い
  • 外部APIとの連携がある
  • 型安全性を重視したい
  • 小〜中規模のチーム

具体的なプロジェクト例:顧客管理システム(CRM)の入力フォーム検証、予約システムのAPI連携とデータ検証、問い合わせフォームのバリデーション、ECサイトの商品登録・注文処理など。

新規プロジェクトやTypeScript移行のタイミングが導入の好機です。

Zodの導入と基本的な使い方

インストールと環境構築

Zodの導入は非常にシンプルです。Node.js(バージョン14以上)とTypeScript(バージョン4.5以上)が必要です。

npm install zod

依存関係がゼロなので、追加のパッケージは不要です。

TypeScript設定(tsconfig.json):

{
  "compilerOptions": {
    "strict": true,
    "strictNullChecks": true,
    "esModuleInterop": true
  }
}

strictモードは、Zodの型推論を正しく機能させるために必須です。

基本的なバリデーションの実装

実際にZodを使ったバリデーションを実装してみましょう。

import { z } from 'zod';

// スキーマ定義
const UserSchema = z.object({
  name: z.string(),
  age: z.number(),
});

// 検証するデータ
const userData = {
  name: '山田太郎',
  age: 30,
};

// バリデーション実行
try {
  const validatedData = UserSchema.parse(userData);
  console.log('検証成功:', validatedData);
} catch (error) {
  console.error('検証失敗:', error);
}

正常なデータの場合は検証に成功し、データがそのまま返されます。不正なデータの場合はエラーがスローされます。

データ型の定義方法

Zodは、JavaScriptの基本的なデータ型すべてに対応しています。

プリミティブ型:

const stringSchema = z.string();
const numberSchema = z.number();
const booleanSchema = z.boolean();
const dateSchema = z.date();

リテラル型とEnum:

// 特定の値のみ許可
const statusSchema = z.enum(['active', 'inactive', 'suspended']);

// 検証
const status = statusSchema.parse('active'); // OK

オブジェクトと配列:

// オブジェクト
const UserSchema = z.object({
  id: z.number(),
  name: z.string(),
  email: z.string().email(),
});

// 配列
const UsersSchema = z.array(UserSchema);

// ネストしたオブジェクト
const CompanySchema = z.object({
  name: z.string(),
  address: z.object({
    prefecture: z.string(),
    city: z.string(),
  }),
});

parseとsafeParseの使い分け

Zodには2つの主要なバリデーションメソッドがあります。

parse:エラーをスローする

try {
  const user = UserSchema.parse(userData);
  console.log(user);
} catch (error) {
  console.error('バリデーションエラー:', error);
}

safeParse:結果オブジェクトを返す

const result = UserSchema.safeParse(userData);

if (result.success) {
  console.log(result.data); // 検証済みデータ
} else {
  console.log(result.error); // エラー情報
}
メソッド 使用場面
parse エラー時に処理を中断したい場合
safeParse エラーを適切にハンドリングしたい場合

実務での推奨パターン:

  • フォーム検証:safeParseでエラーメッセージをUIに表示
  • API内部処理:parseで不正データは即座にエラーレスポンス

エラーハンドリング

Zodのエラー情報は詳細で、ユーザーフレンドリーなメッセージ表示に活用できます。

const result = UserSchema.safeParse(invalidData);

if (!result.success) {
  // フィールドごとのエラーを取得
  const formatted = result.error.flatten();
  console.log(formatted.fieldErrors);
  /*
  {
    name: ['String must contain at least 1 character(s)'],
    age: ['Expected number, received string'],
    email: ['Invalid email']
  }
  */
}

この機能を使えば、フォームの各フィールドに対応するエラーメッセージを簡単に表示できます。

実践的なバリデーションルール

実務で頻繁に使用する具体的なバリデーションルールを解説します。

文字列のバリデーション

// 文字数制限
const nameSchema = z.string()
  .min(1, '名前は必須です')
  .max(50, '名前は50文字以内で入力してください');

// 正規表現
const phoneSchema = z.string().regex(
  /^0\d{9,10}$/,
  '正しい電話番号を入力してください'
);

// 組み込み検証
const emailSchema = z.string().email('正しいメールアドレスを入力してください');
const urlSchema = z.string().url('正しいURLを入力してください');

実務例:お問い合わせフォーム

const ContactFormSchema = z.object({
  companyName: z.string().min(1).max(100),
  personName: z.string().min(1).max(50),
  email: z.string().email('正しいメールアドレスを入力してください'),
  phone: z.string().regex(/^0\d{9,10}$/, '正しい電話番号を入力してください'),
  message: z.string().min(10).max(1000),
});

数値のバリデーション

// 範囲指定
const ageSchema = z.number().min(0).max(150);

// 正の数のみ
const priceSchema = z.number().positive('価格は正の数で入力してください');

// 整数のみ
const quantitySchema = z.number().int('整数で入力してください');

// 倍数指定
const multipleOfFiveSchema = z.number().multipleOf(5);

実務例:商品登録フォーム

const ProductSchema = z.object({
  name: z.string().min(1).max(100),
  price: z.number().positive().int().max(10000000),
  stock: z.number().int().min(0),
  discountRate: z.number().min(0).max(100).optional(),
});

オプショナル・デフォルト値・null許容

// オプショナル(undefined許可)
const schema1 = z.object({
  nickname: z.string().optional(),
});

// デフォルト値
const schema2 = z.object({
  role: z.string().default('user'),
});

// null許可
const schema3 = z.object({
  middleName: z.string().nullable(),
});

// nullとundefined両方許可
const schema4 = z.object({
  bio: z.string().nullish(),
});

Reactでのフォームバリデーション実装

React Hook Formとの連携

Zodは React Hook Form と組み合わせることで、強力なフォームバリデーションを実現できます。

import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';

const formSchema = z.object({
  email: z.string().email('正しいメールアドレスを入力してください'),
  password: z.string().min(8, 'パスワードは8文字以上で入力してください'),
});

type FormData = z.infer<typeof formSchema>;

function LoginForm() {
  const { register, handleSubmit, formState: { errors } } = useForm<FormData>({
    resolver: zodResolver(formSchema),
  });

  const onSubmit = (data: FormData) => {
    console.log(data);
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input {...register('email')} />
      {errors.email && <span>{errors.email.message}</span>}

      <input type="password" {...register('password')} />
      {errors.password && <span>{errors.password.message}</span>}

      <button type="submit">ログイン</button>
    </form>
  );
}

zodResolverを使うことで、Zodのスキーマを React Hook Form に統合できます。エラーメッセージも自動的に連携されます。

リアルタイムバリデーション

const { register, watch, formState: { errors } } = useForm<FormData>({
  resolver: zodResolver(formSchema),
  mode: 'onChange', // リアルタイムバリデーション
});

modeオプションを設定することで、入力中にリアルタイムでバリデーションを実行できます。

応用的な使い方

条件付きバリデーション

refinesuperRefineを使うことで、複雑な条件付きバリデーションを実装できます。

// パスワード確認
const signupSchema = z.object({
  password: z.string().min(8),
  confirmPassword: z.string(),
}).refine((data) => data.password === data.confirmPassword, {
  message: 'パスワードが一致しません',
  path: ['confirmPassword'],
});

// 複数の条件チェック
const orderSchema = z.object({
  quantity: z.number(),
  price: z.number(),
}).superRefine((data, ctx) => {
  if (data.quantity * data.price > 1000000) {
    ctx.addIssue({
      code: z.ZodIssueCode.custom,
      message: '合計金額は100万円以下にしてください',
      path: ['quantity'],
    });
  }
});

スキーマの再利用とコンポジション

// 基本スキーマ
const AddressSchema = z.object({
  prefecture: z.string(),
  city: z.string(),
  street: z.string(),
});

// 拡張
const UserSchema = z.object({
  name: z.string(),
  address: AddressSchema,
});

// マージ
const ExtendedUserSchema = UserSchema.extend({
  phoneNumber: z.string(),
});

// 部分的に使用
const PartialUserSchema = UserSchema.partial();

スキーマを再利用することで、コードの重複を減らし、保守性を向上できます。

よくあるエラーと対処法

つまずきやすいポイント

1. 文字列の数値をバリデーションしたい

フォームからのデータは文字列として送られることが多いため、coerceを使います。

const schema = z.object({
  age: z.coerce.number().min(0).max(150),
});

// "30"という文字列を数値30に変換してバリデーション
schema.parse({ age: "30" }); // OK

2. オプショナルとnullableの違い

// optional: undefinedを許可
z.string().optional() // string | undefined

// nullable: nullを許可
z.string().nullable() // string | null

// nullish: 両方許可
z.string().nullish() // string | null | undefined

3. エラーメッセージの日本語化

const customErrorMap: z.ZodErrorMap = (issue, ctx) => {
  if (issue.code === z.ZodIssueCode.invalid_type) {
    return { message: '正しい形式で入力してください' };
  }
  return { message: ctx.defaultError };
};

z.setErrorMap(customErrorMap);

パフォーマンスの最適化

大量のデータを検証する場合は、以下の点に注意します。

  • 不要な検証ルールを追加しない
  • 複雑なrefineは最小限に
  • スキーマは再利用してインスタンス化を減らす

まとめ

Zodは、TypeScriptプロジェクトにおいて型安全なバリデーションを実現する強力なライブラリです。

Zodバリデーション使い方のポイント:

  • 一つのスキーマ定義で型情報とバリデーションロジックの両方を管理
  • parsesafeParseを適切に使い分ける
  • React Hook Formと組み合わせることで、実用的なフォームバリデーションを実現
  • refinesuperRefineで複雑な条件付きバリデーションも可能

Zodを活用することで、開発効率を向上させながら、堅牢なデータ検証を実装できます。

次のステップ:

  • Zod公式ドキュメントで詳細な機能を確認
  • React Hook Formとの連携パターンを実践
  • 実際のプロジェクトで段階的に導入

業務システム開発でバリデーションの実装にお困りの場合は、Harmonic Societyがサポートいたします。TypeScriptとZodを活用した型安全な開発支援から、フォームバリデーションの実装まで、お気軽にご相談ください。

#Zod#バリデーション#使い方
共有:

ちょっとした業務の悩みも、気軽にご相談ください。

まずは話だけ聞いてもらう