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 schema validation with static type inference」として開発されたバリデーションライブラリです。TypeScriptの型システムと完全に統合され、ランタイムでのデータ検証と静的型安全性を同時に提供します。スキーマ定義から自動的にTypeScript型を推論し、コンパイル時とランタイムの両方で型安全性を保証。2025年現在、AI開発やモダンWebアプリケーションにおいて最も注目されているTypeScriptバリデーションライブラリです。
詳細
Zod 4は2025年現在の最新安定版で、TypeScript-firstのアプローチにより「型とバリデーションの単一ソース」を実現します。従来のTypeScriptは静的型チェックのみでランタイム型安全性を提供しませんが、Zodはこの問題を解決。スキーマからTypeScript型を自動生成し、ランタイムでの入力データ検証を同一のスキーマで実行します。31k+のGitHubスターを獲得し、tRPC、Next.js、AI開発エコシステムで広く採用されています。
主な特徴
- TypeScript型推論: スキーマ定義から自動的にTypeScript型を生成
- ランタイム型安全性: 実行時のデータ型検証でTypeScript型システムを補完
- ゼロ依存関係: 外部依存なしで軽量かつ高速
- 関数型アプローチ: イミュータブルなスキーマオブジェクトによる安全な操作
- 豊富な型サポート: プリミティブ、オブジェクト、配列、Union、関数まで幅広い型対応
- カスタムバリデーション: 高度なバリデーションロジックの柔軟な定義
メリット・デメリット
メリット
- TypeScript開発者にとって最も直感的なAPI設計
- コンパイル時とランタイムの型安全性を同時に保証
- AI開発やtRPCエコシステムでの標準的選択肢
- tree-shakingによる最小バンドルサイズ
- 豊富なコミュニティエコシステム(NextJS、Remix等で採用)
- エラーメッセージの詳細性とカスタマイズ性
デメリット
- TypeScript専用(純粋なJavaScriptでは使用不可)
- 学習コストがやや高い(特に複雑なスキーマ定義)
- 大規模なスキーマでのパフォーマンスオーバーヘッド
- 他言語での相互運用性が限定的
- 型推論の複雑性により開発環境への負荷
- 純粋なJavaScriptプロジェクトでは恩恵が少ない
参考ページ
書き方の例
インストールと基本セットアップ
# Zodのインストール
npm install zod
yarn add zod
pnpm add zod
bun add zod
deno add npm:zod
# TypeScript 5.5以降が必要
npm install -D typescript
基本的なスキーマ定義とバリデーション
import { z } from "zod/v4";
// 基本的なスキーマ定義
const UserSchema = z.object({
name: z.string(),
age: z.number(),
email: z.string().email(),
});
// 型推論の自動生成
type User = z.infer<typeof UserSchema>;
// type User = { name: string; age: number; email: string; }
// データのバリデーション
const userData = { name: "田中太郎", age: 30, email: "[email protected]" };
// parse - 失敗時は例外をthrow
try {
const validUser = UserSchema.parse(userData);
console.log(validUser); // { name: "田中太郎", age: 30, email: "[email protected]" }
} catch (error) {
console.error("バリデーションエラー:", error);
}
// safeParse - 失敗時も例外を投げない
const result = UserSchema.safeParse(userData);
if (result.success) {
console.log("バリデーション成功:", result.data);
} else {
console.log("バリデーション失敗:", result.error);
}
// プリミティブ型の例
const stringSchema = z.string();
const numberSchema = z.number();
const booleanSchema = z.boolean();
const dateSchema = z.date();
stringSchema.parse("Hello"); // "Hello"
numberSchema.parse(42); // 42
booleanSchema.parse(true); // true
高度なバリデーションルールとカスタムバリデーター
// 文字列の詳細バリデーション
const PasswordSchema = z.string()
.min(8, "パスワードは8文字以上である必要があります")
.max(50, "パスワードは50文字以下である必要があります")
.regex(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/, "パスワードは大文字、小文字、数字を含む必要があります");
// 数値の範囲指定
const AgeSchema = z.number()
.int("年齢は整数である必要があります")
.min(0, "年齢は0以上である必要があります")
.max(150, "年齢は150以下である必要があります");
// 配列とオブジェクトの組み合わせ
const ProductSchema = z.object({
id: z.string().uuid(),
name: z.string().min(1),
price: z.number().positive(),
categories: z.array(z.string()),
metadata: z.record(z.string(), z.any()).optional(),
});
// Union型(複数の型の中から一つ)
const StatusSchema = z.union([
z.literal("pending"),
z.literal("completed"),
z.literal("failed"),
]);
// Enum型の定義
const RoleSchema = z.enum(["admin", "user", "guest"]);
// カスタムバリデーション
const CustomEmailSchema = z.string().refine(
(email) => {
// カスタムバリデーションロジック
return email.includes("@") && email.endsWith(".com");
},
{
message: "有効な.comドメインのメールアドレスを入力してください",
}
);
// 複数条件の詳細バリデーション
const UserRegistrationSchema = z.object({
username: z.string()
.min(3, "ユーザー名は3文字以上である必要があります")
.max(20, "ユーザー名は20文字以下である必要があります")
.regex(/^[a-zA-Z0-9_]+$/, "ユーザー名は英数字とアンダースコアのみ使用可能です"),
email: z.string().email("有効なメールアドレスを入力してください"),
password: PasswordSchema,
confirmPassword: z.string(),
agreeToTerms: z.boolean().refine(val => val === true, {
message: "利用規約に同意する必要があります",
}),
}).refine(data => data.password === data.confirmPassword, {
message: "パスワードが一致しません",
path: ["confirmPassword"],
});
フレームワーク統合(React、NestJS、FastAPI等)
// React Hook Formとの統合
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
const UserFormSchema = z.object({
name: z.string().min(1, "名前は必須です"),
email: z.string().email("有効なメールアドレスを入力してください"),
age: z.number().min(18, "18歳以上である必要があります"),
});
function UserForm() {
const { register, handleSubmit, formState: { errors } } = useForm({
resolver: zodResolver(UserFormSchema),
});
const onSubmit = (data: z.infer<typeof UserFormSchema>) => {
console.log("送信データ:", data);
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input {...register("name")} placeholder="名前" />
{errors.name && <span>{errors.name.message}</span>}
<input {...register("email")} placeholder="メールアドレス" type="email" />
{errors.email && <span>{errors.email.message}</span>}
<input {...register("age", { valueAsNumber: true })} placeholder="年齢" type="number" />
{errors.age && <span>{errors.age.message}</span>}
<button type="submit">送信</button>
</form>
);
}
// tRPCとの統合例
import { z } from "zod";
import { publicProcedure, router } from "./trpc";
const CreateUserInput = z.object({
name: z.string(),
email: z.string().email(),
age: z.number().int().min(0),
});
export const appRouter = router({
createUser: publicProcedure
.input(CreateUserInput)
.mutation(async ({ input }) => {
// inputは自動的に型安全
const user = await createUser(input);
return user;
}),
});
// Express.jsでのミドルウェア例
import express from "express";
const validateRequest = (schema: z.ZodType) => {
return (req: express.Request, res: express.Response, next: express.NextFunction) => {
try {
schema.parse(req.body);
next();
} catch (error) {
res.status(400).json({ error: "バリデーションエラー", details: error });
}
};
};
app.post("/users", validateRequest(UserRegistrationSchema), (req, res) => {
// req.bodyは型安全にアクセス可能
res.json({ message: "ユーザー作成成功" });
});
エラーハンドリングとカスタムエラーメッセージ
// 詳細なエラーハンドリング
const schema = z.object({
name: z.string().min(2, "名前は2文字以上である必要があります"),
age: z.number().min(0, "年齢は0以上である必要があります"),
});
const result = schema.safeParse({ name: "A", age: -1 });
if (!result.success) {
console.log("エラー詳細:", result.error.errors);
/*
[
{
code: "too_small",
minimum: 2,
type: "string",
inclusive: true,
exact: false,
message: "名前は2文字以上である必要があります",
path: ["name"]
},
{
code: "too_small",
minimum: 0,
type: "number",
inclusive: true,
exact: false,
message: "年齢は0以上である必要があります",
path: ["age"]
}
]
*/
}
// カスタムエラーメッセージのグローバル設定
z.setErrorMap((issue, ctx) => {
switch (issue.code) {
case z.ZodIssueCode.invalid_type:
if (issue.expected === "string") {
return { message: "文字列を入力してください" };
}
break;
case z.ZodIssueCode.too_small:
if (issue.type === "string") {
return { message: `${issue.minimum}文字以上入力してください` };
}
break;
default:
return { message: ctx.defaultError };
}
return { message: ctx.defaultError };
});
// フォーム用エラーハンドリングヘルパー
function getFieldErrors(error: z.ZodError): Record<string, string> {
const fieldErrors: Record<string, string> = {};
error.errors.forEach((err) => {
if (err.path.length > 0) {
const fieldName = err.path.join(".");
fieldErrors[fieldName] = err.message;
}
});
return fieldErrors;
}
// 使用例
const validationResult = schema.safeParse(userData);
if (!validationResult.success) {
const fieldErrors = getFieldErrors(validationResult.error);
console.log(fieldErrors); // { "name": "名前は2文字以上である必要があります", "age": "年齢は0以上である必要があります" }
}
型安全性とTypeScript統合
// 高度な型推論の例
const NestedSchema = z.object({
user: z.object({
profile: z.object({
name: z.string(),
settings: z.record(z.string(), z.boolean()),
}),
posts: z.array(z.object({
id: z.number(),
title: z.string(),
tags: z.array(z.string()),
})),
}),
metadata: z.object({
created: z.date(),
updated: z.date().optional(),
}),
});
type NestedType = z.infer<typeof NestedSchema>;
/*
type NestedType = {
user: {
profile: {
name: string;
settings: Record<string, boolean>;
};
posts: Array<{
id: number;
title: string;
tags: string[];
}>;
};
metadata: {
created: Date;
updated?: Date | undefined;
};
}
*/
// 関数の型安全なバリデーション
const createUserFunction = z.function()
.args(z.string(), z.number(), z.boolean().optional())
.returns(z.object({ id: z.string(), success: z.boolean() }));
type CreateUserFunction = z.infer<typeof createUserFunction>;
// type CreateUserFunction = (arg_0: string, arg_1: number, arg_2?: boolean) => { id: string; success: boolean; }
// 実装の型安全性
const createUser = createUserFunction.implement((name, age, isActive = true) => {
// TypeScriptが引数の型を自動推論
return {
id: `user_${Date.now()}`,
success: true,
};
});
// 条件付きスキーマ(discriminated unions)
const AnimalSchema = z.discriminatedUnion("type", [
z.object({
type: z.literal("dog"),
breed: z.string(),
bark: z.boolean(),
}),
z.object({
type: z.literal("cat"),
indoor: z.boolean(),
meow: z.string(),
}),
]);
type Animal = z.infer<typeof AnimalSchema>;
// TypeScriptは自動的にunion型を推論
// 変換(transform)と前処理(preprocess)
const StringToNumberSchema = z.string()
.transform(val => parseInt(val, 10))
.refine(val => !isNaN(val), "有効な数値文字列を入力してください");
const PreprocessedSchema = z.preprocess(
(input) => {
// 前処理: 文字列をトリムして小文字に変換
if (typeof input === "string") {
return input.trim().toLowerCase();
}
return input;
},
z.string().min(1)
);
// 遅延評価(Lazy evaluation)- 再帰的なスキーマ
interface Category {
id: string;
name: string;
parent?: Category;
children: Category[];
}
const CategorySchema: z.ZodType<Category> = z.lazy(() =>
z.object({
id: z.string(),
name: z.string(),
parent: CategorySchema.optional(),
children: z.array(CategorySchema),
})
);
APIスキーマとドキュメント生成
// OpenAPI/JSON Schema生成例
import { zodToJsonSchema } from "zod-to-json-schema";
const UserAPISchema = z.object({
name: z.string().describe("ユーザーの名前"),
email: z.string().email().describe("ユーザーのメールアドレス"),
age: z.number().min(0).max(150).describe("ユーザーの年齢"),
role: z.enum(["admin", "user", "guest"]).describe("ユーザーの役割"),
});
// JSON Schemaに変換
const jsonSchema = zodToJsonSchema(UserAPISchema, "UserAPI");
console.log(JSON.stringify(jsonSchema, null, 2));
// APIドキュメント生成用のメタデータ
const APIEndpoints = {
createUser: {
method: "POST",
path: "/users",
requestSchema: UserAPISchema,
responseSchema: z.object({
id: z.string().uuid(),
...UserAPISchema.shape,
createdAt: z.date(),
}),
},
getUser: {
method: "GET",
path: "/users/:id",
paramsSchema: z.object({
id: z.string().uuid(),
}),
responseSchema: UserAPISchema.extend({
id: z.string().uuid(),
createdAt: z.date(),
updatedAt: z.date(),
}),
},
};
// 型安全なAPIクライアント生成
type APIClient = {
[K in keyof typeof APIEndpoints]: (
...args: any[]
) => Promise<z.infer<typeof APIEndpoints[K]["responseSchema"]>>;
};