Zod
GitHub概要
スター40,431
ウォッチ75
フォーク1,649
作成日:2020年3月7日
言語:TypeScript
ライセンス:MIT License
トピックス
runtime-validationschema-validationstatic-typestype-inferencetypescript
スター履歴
データ取得日時: 2025/10/22 09:49
Zod
概要
ZodはTypeScript-firstのスキーマ宣言とバリデーションライブラリです。型推論により、一度スキーマを定義すれば、Zodが自動的にTypeScriptの型を推論します。これにより、実行時の検証と静的型付けの両方を単一のスキーマ定義から得ることができ、型の安全性を保証しながら外部データの検証を行えます。
特徴
- TypeScript-first設計: 完全な型推論サポート
- ゼロ依存: 外部ライブラリに依存しない
- 小さなバンドルサイズ: コアバンドルは2kb (gzip圧縮後)
- イミュータブルAPI: メソッドチェーンによる宣言的なスキーマ定義
- 包括的な検証: プリミティブ型から複雑なオブジェクトまで対応
- エラーハンドリング: 詳細なエラー情報とカスタマイズ可能なメッセージ
- JSON Schema変換: スキーマをJSON Schemaに変換可能
- トランスフォーメーション: バリデーション後のデータ変換機能
インストール
npm install zod
# または
yarn add zod
# または
pnpm add zod
# または
bun add zod
使用例
基本的な使用方法
import { z } from 'zod';
// スキーマの定義
const UserSchema = z.object({
name: z.string(),
age: z.number().min(0).max(150),
email: z.string().email(),
isActive: z.boolean().default(true),
});
// TypeScriptの型を推論
type User = z.infer<typeof UserSchema>;
// { name: string; age: number; email: string; isActive: boolean; }
// データの検証
try {
const userData = UserSchema.parse({
name: "山田太郎",
age: 30,
email: "[email protected]"
});
console.log("検証成功:", userData);
} catch (error) {
if (error instanceof z.ZodError) {
console.error("検証エラー:", error.errors);
}
}
// 安全な検証(例外を投げない)
const result = UserSchema.safeParse(inputData);
if (result.success) {
console.log("有効なデータ:", result.data);
} else {
console.log("検証エラー:", result.error);
}
高度なスキーマ定義
// 列挙型
const StatusSchema = z.enum(["pending", "processing", "completed", "failed"]);
// ユニオン型
const IdSchema = z.union([z.string(), z.number()]);
// 条件付きスキーマ
const ProductSchema = z.object({
type: z.enum(["physical", "digital"]),
name: z.string(),
price: z.number().positive(),
weight: z.number().optional(), // physicalの場合のみ必要
downloadUrl: z.string().url().optional(), // digitalの場合のみ必要
}).refine((data) => {
if (data.type === "physical" && !data.weight) {
return false;
}
if (data.type === "digital" && !data.downloadUrl) {
return false;
}
return true;
}, {
message: "物理製品には重量が、デジタル製品にはダウンロードURLが必要です"
});
// 配列とタプル
const TagsSchema = z.array(z.string()).min(1).max(5);
const CoordinatesSchema = z.tuple([z.number(), z.number()]); // [緯度, 経度]
// Record型とMap型
const ScoresSchema = z.record(z.string(), z.number());
const ConfigSchema = z.map(z.string(), z.any());
// 再帰的スキーマ
type Category = {
name: string;
subcategories: Category[];
};
const CategorySchema: z.ZodType<Category> = z.object({
name: z.string(),
subcategories: z.lazy(() => z.array(CategorySchema)),
});
トランスフォーメーション
// 文字列を数値に変換
const StringToNumberSchema = z.string().transform((val) => parseInt(val, 10));
// 日付の処理
const DateSchema = z.string().datetime().transform((str) => new Date(str));
// カスタム変換とバリデーション
const PasswordSchema = z.string()
.min(8, "パスワードは8文字以上必要です")
.refine((password) => /[A-Z]/.test(password), {
message: "大文字を1文字以上含む必要があります",
})
.refine((password) => /[0-9]/.test(password), {
message: "数字を1文字以上含む必要があります",
})
.refine((password) => /[!@#$%^&*]/.test(password), {
message: "特殊文字を1文字以上含む必要があります",
});
// オブジェクトの正規化
const UserInputSchema = z.object({
name: z.string().trim(),
email: z.string().email().toLowerCase(),
age: z.union([z.string(), z.number()]).transform((val) => {
if (typeof val === "string") return parseInt(val, 10);
return val;
}),
});
カスタムエラーメッセージ
const LoginSchema = z.object({
username: z.string({
required_error: "ユーザー名は必須です",
invalid_type_error: "ユーザー名は文字列である必要があります",
}).min(3, { message: "ユーザー名は3文字以上必要です" }),
password: z.string({
required_error: "パスワードは必須です",
}).min(8, { message: "パスワードは8文字以上必要です" }),
});
// グローバルエラーマップのカスタマイズ
const customErrorMap: z.ZodErrorMap = (issue, ctx) => {
if (issue.code === z.ZodIssueCode.invalid_type) {
if (issue.expected === "string") {
return { message: "文字列を入力してください" };
}
}
if (issue.code === z.ZodIssueCode.custom) {
return { message: `カスタムエラー: ${issue.message}` };
}
return { message: ctx.defaultError };
};
z.setErrorMap(customErrorMap);
API統合の例
// Express.jsでの使用例
import express from 'express';
const app = express();
app.use(express.json());
const CreateUserSchema = z.object({
body: z.object({
name: z.string().min(1),
email: z.string().email(),
age: z.number().int().positive(),
}),
});
// ミドルウェアとして使用
const validateRequest = (schema: z.ZodSchema) => {
return (req: express.Request, res: express.Response, next: express.NextFunction) => {
const result = schema.safeParse({
body: req.body,
query: req.query,
params: req.params,
});
if (!result.success) {
return res.status(400).json({
error: "検証エラー",
details: result.error.flatten(),
});
}
req.body = result.data.body;
next();
};
};
app.post('/users', validateRequest(CreateUserSchema), (req, res) => {
// req.bodyは型安全
const user = req.body; // { name: string; email: string; age: number }
// ユーザー作成処理
res.json({ success: true, user });
});
フォーム検証との統合
// React Hook Formとの統合例
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
const FormSchema = z.object({
username: z.string().min(3, "ユーザー名は3文字以上"),
email: z.string().email("有効なメールアドレスを入力してください"),
age: z.number().min(18, "18歳以上である必要があります"),
acceptTerms: z.boolean().refine((val) => val === true, {
message: "利用規約に同意する必要があります",
}),
});
type FormData = z.infer<typeof FormSchema>;
function RegistrationForm() {
const {
register,
handleSubmit,
formState: { errors },
} = useForm<FormData>({
resolver: zodResolver(FormSchema),
});
const onSubmit = (data: FormData) => {
console.log("フォームデータ:", data);
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input {...register("username")} placeholder="ユーザー名" />
{errors.username && <p>{errors.username.message}</p>}
<input {...register("email")} placeholder="メールアドレス" />
{errors.email && <p>{errors.email.message}</p>}
<input {...register("age", { valueAsNumber: true })} type="number" placeholder="年齢" />
{errors.age && <p>{errors.age.message}</p>}
<label>
<input {...register("acceptTerms")} type="checkbox" />
利用規約に同意する
</label>
{errors.acceptTerms && <p>{errors.acceptTerms.message}</p>}
<button type="submit">登録</button>
</form>
);
}
環境変数の検証
const EnvSchema = z.object({
NODE_ENV: z.enum(["development", "production", "test"]),
PORT: z.string().transform((val) => parseInt(val, 10)),
DATABASE_URL: z.string().url(),
JWT_SECRET: z.string().min(32),
REDIS_URL: z.string().url().optional(),
LOG_LEVEL: z.enum(["debug", "info", "warn", "error"]).default("info"),
});
// 環境変数を検証
const env = EnvSchema.parse(process.env);
// 型安全な環境変数のエクスポート
export const config = {
nodeEnv: env.NODE_ENV,
port: env.PORT,
databaseUrl: env.DATABASE_URL,
jwtSecret: env.JWT_SECRET,
redisUrl: env.REDIS_URL,
logLevel: env.LOG_LEVEL,
} as const;
比較・代替手段
類似ライブラリとの比較
- Yup: より成熟しているが、TypeScript統合が弱い
- Joi: Node.js向けに最適化されているが、ブラウザでは重い
- io-ts: 関数型プログラミング指向で、学習曲線が急
- Superstruct: よりシンプルだが、機能が限定的
- Valibot: より軽量だが、エコシステムが小さい
Zodを選ぶべき場合
- TypeScriptプロジェクトで型推論を重視
- 実行時検証と静的型付けの統一が必要
- モダンなAPIとDXを求める
- フロントエンドとバックエンドで同じ検証ロジックを共有したい
- JSON Schemaとの相互運用が必要
学習リソース
まとめ
ZodはTypeScriptエコシステムにおける最も人気のあるバリデーションライブラリの一つです。型推論の優れたサポート、包括的な機能セット、優れた開発者体験により、TypeScriptプロジェクトでの実行時検証の標準的な選択肢となっています。特に、APIの入力検証、フォームバリデーション、環境変数の検証など、外部データの型安全な処理が必要なあらゆる場面で威力を発揮します。