zod-validation-utilities について
Zod v4で再利用可能かつ型安全なバリデーションを構築する、TypeScript開発者向けスキルです。エラー処理、入力変換、React Hook Formとの連携を扱います。
主な機能
- 01Zod v4のスキーマ定義
- 02入力の強制変換と変換処理
- 03refineによる項目間検証
- 04safeParseによるエラー処理
- 05React Hook Formとの連携
- 06入力型と出力型の推論
活用シーン
- 01APIリクエストとレスポンスの検証
- 02フォームや環境変数の解析
- 03チーム間での検証スキーマ再利用
- 04React Hook Formでのフォーム検証
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ giuseppe-trisciuoglio/ developer-kit --skill zod-validation-utilitiesインストール後、Claude Code で/zod-validation-utilitiesを入力して読み込まれたか確認します。
同梱ファイルreferences/advanced-patterns.md
詳細
Zod バリデーションユーティリティ
概要
最小限の定型コードで再利用可能かつ型安全なバリデーションを実現する、実運用向けの Zod v4 パターンです。最新の API、予測可能なエラー処理、フォーム連携に重点を置いています。
使用する場面
- TypeScript サービスでリクエスト/レスポンスのバリデーションスキーマを定義する
- API、フォーム、環境変数、外部システムからの信頼できない入力を解析する
- 型強制、変換、フィールド間のバリデーションを標準化する
- チーム間で再利用可能なスキーマユーティリティを構築する
zodResolverを使用して React Hook Form と Zod を連携する
手順
- 厳密なオブジェクトスキーマと明示的なフィールド制約から始めます
- エラーメッセージには、最新の Zod v4 API と
errorオプションを優先します - 入力型が不確かな境界では型強制(
z.coerce.*)を使用します - ビジネス上の不変条件は、スキーマ定義の近くにある
refine/superRefineに記述します - 一貫性を保つため、スキーマと推論された型(
z.input/z.output)の両方をエクスポートします - 重複を減らすため、ユーティリティスキーマ(メール、ID、日付、ページネーション)を再利用します
バリデーションのワークフロー
API ハンドラーまたはサービスにバリデーションを組み込む場合:
- 定義:境界(ハンドラー、キュー、設定ローダー)でスキーマを定義します
- 解析:
safeParseを使ってエラーを適切に処理します - 確認:
result.successを確認し、失敗時と成功時で処理を分岐します - 使用:成功時の処理では、型推論が完全に適用された
result.dataを使用します - 返却:フォーマット済みのエラーを返すか、バリデーション済みのデータを使って処理を続行します
完全なパターンについては、例 7(safeParse のワークフロー)を参照してください。
例
1) 最新の Zod 4 のプリミティブとオブジェクトのエラー
import { z } from "zod";
export const UserIdSchema = z.uuid({ error: "Invalid user id" });
export const EmailSchema = z.email({ error: "Invalid email" });
export const WebsiteSchema = z.url({ error: "Invalid URL" });
export const UserProfileSchema = z.object(
{
id: UserIdSchema,
email: EmailSchema,
website: WebsiteSchema.optional(),
},
{ error: "Invalid user profile payload" }
);
2) 型強制、前処理、変換
import { z } from "zod";
export const PaginationQuerySchema = z.object({
page: z.coerce.number().int().min(1).default(1),
pageSize: z.coerce.number().int().min(1).max(100).default(20),
includeArchived: z.coerce.boolean().default(false),
});
export const DateFromUnknownSchema = z.preprocess(
(value) => (typeof value === "string" || value instanceof Date ? value : undefined),
z.coerce.date({ error: "Invalid date" })
);
export const NormalizedEmailSchema = z
.string()
.trim()
.toLowerCase()
.email({ error: "Invalid email" })
.transform((value) => value as Lowercase<string>);
3) 複雑なスキーマ構造
import { z } from "zod";
const TagSchema = z.string().trim().min(1).max(40);
export const ProductSchema = z.object({
sku: z.string().min(3).max(24),
tags: z.array(TagSchema).max(15),
attributes: z.record(z.string(), z.union([z.string(), z.number(), z.boolean()])),
dimensions: z.tuple([z.number().positive(), z.number().positive(), z.number().positive()]),
});
export const PaymentMethodSchema = z.discriminatedUnion("type", [
z.object({ type: z.literal("card"), last4: z.string().regex(/^\d{4}$/) }),
z.object({ type: z.literal("paypal"), email: z.email() }),
z.object({ type: z.literal("wire"), iban: z.string().min(10) }),
]);
4) refine と superRefine
import { z } from "zod";
export const PasswordSchema = z
.string()
.min(12)
.refine((v) => /[A-Z]/.test(v), { error: "Must include an uppercase letter" })
.refine((v) => /\d/.test(v), { error: "Must include a number" });
export const RegisterSchema = z
.object({
email: z.email(),
password: PasswordSchema,
confirmPassword: z.string(),
})
.superRefine((data, ctx) => {
if (data.password !== data.confirmPassword) {
ctx.addIssue({
code: "custom",
path: ["confirmPassword"],
message: "Passwords do not match",
});
}
});
5) オプショナル、nullable、nullish、デフォルト値
import { z } from "zod";
export const UserPreferencesSchema = z.object({
nickname: z.string().min(2).optional(), // undefined allowed
bio: z.string().max(280).nullable(), // null allowed
avatarUrl: z.url().nullish(), // null or undefined allowed
locale: z.string().default("en"), // fallback when missing
});
6) React Hook Form との連携(zodResolver)
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
const ProfileFormSchema = z.object({
name: z.string().min(2, { error: "Name too short" }),
email: z.email({ error: "Invalid email" }),
age: z.coerce.number().int().min(18),
});
type ProfileFormInput = z.input<typeof ProfileFormSchema>;
type ProfileFormOutput = z.output<typeof ProfileFormSchema>;
const form = useForm<ProfileFormInput, unknown, ProfileFormOutput>({
resolver: zodResolver(ProfileFormSchema),
criteriaMode: "all",
});
7) safeParse によるエラー処理のワークフロー
import { z } from "zod";
import type { ZodError } from "zod";
const ResultSchema = z.object({ id: z.string(), name: z.string() });
function parseAndHandle(input: unknown) {
const result = ResultSchema.safeParse(input);
if (!result.success) {
const error = result.error as ZodError;
console.error("Validation failed:", error.errors);
return { success: false as const, error: error.format() };
}
return { success: true as const, data: result.data };
}
ヒント:高度な判別可能ユニオンのパターンや複雑な React Hook Form のワークフローについては、
references/advanced-patterns.mdを参照してください。
ベストプラクティス
- スキーマは境界(HTTP ハンドラー、キュー、設定ローダー)の近くに配置します
- 復旧可能なフローでは
safeParseを、即時に失敗させる実行ではparseを優先します - 一貫性を確保するため、小さなスキーマユーティリティ(
id、email、slug)を共有します - 変換や型強制によって実行時の形状が変わる場合は、
z.inputとz.outputを使用します preprocessの過剰な使用は避け、可能な限り明示的なz.coerce.*を優先します- 外部ペイロードは信頼できないものとして扱い、使用前に必ずバリデーションします
制約と警告
- 例がインストール済みの
zodのメジャーバージョンと一致していることを確認します(ここでは v4 API を使用しています) - Zod v4 のパターンでは、カスタムエラーに
errorオプションを使用することを推奨します - 判別可能ユニオンでは、バリアント間で安定した判別キーが必要です
- 型強制によって上流の不正なデータが隠れる可能性があるため、上限・下限や追加の検証を慎重に設定します
コメント
Markdown 対応