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オプションを設定することで、入力中にリアルタイムでバリデーションを実行できます。
応用的な使い方
条件付きバリデーション
refineやsuperRefineを使うことで、複雑な条件付きバリデーションを実装できます。
// パスワード確認
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バリデーション使い方のポイント:
- 一つのスキーマ定義で型情報とバリデーションロジックの両方を管理
parseとsafeParseを適切に使い分ける- React Hook Formと組み合わせることで、実用的なフォームバリデーションを実現
refineやsuperRefineで複雑な条件付きバリデーションも可能
Zodを活用することで、開発効率を向上させながら、堅牢なデータ検証を実装できます。
次のステップ:
- Zod公式ドキュメントで詳細な機能を確認
- React Hook Formとの連携パターンを実践
- 実際のプロジェクトで段階的に導入
業務システム開発でバリデーションの実装にお困りの場合は、Harmonic Societyがサポートいたします。TypeScriptとZodを活用した型安全な開発支援から、フォームバリデーションの実装まで、お気軽にご相談ください。