klh-zod-validation
Zod schema validation patterns. Use when validating API inputs and data.
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
メニュー
Zod schema validation patterns. Use when validating API inputs and data.
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
SOC 職業分類に基づく
This skill should be used when the user asks to "create AGENTS.md", "update AGENTS.md", "maintain agent docs", "set up CLAUDE.md", or needs to keep agent instructions concise. Guides discovery of local skills and enforces minimal documentation style.
This skill should be used when the user asks ANYTHING that will elicit a response. Guides verbosity of output.
Use when listing files, searching file contents, finding files by name, or viewing files in terminal. Replace slow standard tools (ls, grep, find, cat) with faster modern alternatives.
Simplifies and refines code for clarity, consistency, and maintainability while preserving all functionality. Use when asked to "simplify code", "clean up code", "refactor for clarity", "improve readability", or review recently modified code for elegance. Focuses on project-specific best practices.
Core component library and design system patterns. Use when building UI, using design tokens, or working with the component library.
Single entry-point orchestrator that detects task type and dispatches to the right klh-* skill. Use when the user says "dispatch", "klh this", or gives a task that could match multiple skills.
| name | klh-zod-validation |
| description | Zod schema validation patterns. Use when validating API inputs and data. |
Part of klh/skills — personal agent skills by Klaus L. Hougesen. Install:
npx skills add klh/skills --skill klh-zod-validation
Zod schema validation patterns for type-safe data validation.
This skill covers Zod schema validation for type-safe data validation.
Use this skill when:
VALIDATE AT BOUNDARIES - Validate all external input. Trust internal data. Use Zod for type inference.
npm install zod
import { z } from 'zod';
// Primitives
const stringSchema = z.string();
const numberSchema = z.number();
const booleanSchema = z.boolean();
const dateSchema = z.date();
// String validations
const emailSchema = z.string().email();
const urlSchema = z.string().url();
const uuidSchema = z.string().uuid();
const minLengthSchema = z.string().min(1).max(100);
const regexSchema = z.string().regex(/^[a-z]+$/);
// Number validations
const positiveSchema = z.number().positive();
const intSchema = z.number().int();
const rangeSchema = z.number().min(0).max(100);
// Optional and nullable
const optionalSchema = z.string().optional(); // string | undefined
const nullableSchema = z.string().nullable(); // string | null
const nullishSchema = z.string().nullish(); // string | null | undefined
const UserSchema = z.object({
id: z.string().uuid(),
email: z.string().email(),
name: z.string().min(1).max(100),
age: z.number().int().positive().optional(),
role: z.enum(['USER', 'ADMIN', 'MODERATOR']),
createdAt: z.date(),
});
// Type inference
type User = z.infer<typeof UserSchema>;
// Partial (all fields optional)
const PartialUserSchema = UserSchema.partial();
// Pick specific fields
const UserEmailSchema = UserSchema.pick({ email: true, name: true });
// Omit fields
const UserWithoutIdSchema = UserSchema.omit({ id: true, createdAt: true });
// Extend schema
const UserWithProfileSchema = UserSchema.extend({
profile: z.object({
bio: z.string().optional(),
avatar: z.string().url().optional(),
}),
});
// Arrays
const StringArraySchema = z.array(z.string());
const NumberArraySchema = z.array(z.number()).min(1).max(10);
// Tuples
const CoordinatesSchema = z.tuple([z.number(), z.number()]);
// Unions
const StringOrNumberSchema = z.union([z.string(), z.number()]);
const ResultSchema = z.discriminatedUnion('status', [
z.object({ status: z.literal('success'), data: z.unknown() }),
z.object({ status: z.literal('error'), error: z.string() }),
]);
// Enums
const RoleSchema = z.enum(['USER', 'ADMIN', 'MODERATOR']);
type Role = z.infer<typeof RoleSchema>; // 'USER' | 'ADMIN' | 'MODERATOR'
// Create user request
const CreateUserSchema = z.object({
email: z.string().email('Invalid email address'),
password: z.string()
.min(8, 'Password must be at least 8 characters')
.regex(/[A-Z]/, 'Password must contain uppercase letter')
.regex(/[0-9]/, 'Password must contain number'),
name: z.string().min(1, 'Name is required').max(100),
});
// Update user request (all fields optional)
const UpdateUserSchema = CreateUserSchema.partial();
// Query parameters
const PaginationSchema = z.object({
page: z.coerce.number().int().positive().default(1),
perPage: z.coerce.number().int().min(1).max(100).default(20),
sort: z.enum(['asc', 'desc']).default('desc'),
});
// Path parameters
const IdParamSchema = z.object({
id: z.string().uuid('Invalid ID format'),
});
// Transform during parse
const TrimmedStringSchema = z.string().trim();
const LowercaseEmailSchema = z.string().email().toLowerCase();
// Transform to different type
const DateStringSchema = z.string().transform((str) => new Date(str));
// Coerce types
const CoercedNumberSchema = z.coerce.number(); // "42" -> 42
const CoercedDateSchema = z.coerce.date(); // "2024-01-01" -> Date
// Complex transformation
const UserInputSchema = z.object({
email: z.string().email().toLowerCase().trim(),
name: z.string().trim(),
tags: z.string().transform((str) => str.split(',').map((t) => t.trim())),
});
// Custom refinement
const PasswordSchema = z.string()
.min(8)
.refine(
(password) => /[A-Z]/.test(password),
{ message: 'Password must contain uppercase letter' }
)
.refine(
(password) => /[0-9]/.test(password),
{ message: 'Password must contain number' }
);
// Cross-field validation
const PasswordConfirmSchema = z.object({
password: z.string().min(8),
confirmPassword: z.string(),
}).refine(
(data) => data.password === data.confirmPassword,
{
message: 'Passwords do not match',
path: ['confirmPassword'],
}
);
// Async validation
const UniqueEmailSchema = z.string().email().refine(
async (email) => {
const exists = await checkEmailExists(email);
return !exists;
},
{ message: 'Email already registered' }
);
// src/routes/users.ts
import { FastifyPluginAsync } from 'fastify';
import { z } from 'zod';
import { zodToJsonSchema } from 'zod-to-json-schema';
const CreateUserSchema = z.object({
email: z.string().email(),
name: z.string().min(1),
password: z.string().min(8),
});
const UserResponseSchema = z.object({
id: z.string(),
email: z.string(),
name: z.string(),
createdAt: z.string(),
});
type CreateUserInput = z.infer<typeof CreateUserSchema>;
type UserResponse = z.infer<typeof UserResponseSchema>;
const usersRoutes: FastifyPluginAsync = async (fastify) => {
// Manual validation
fastify.post<{ Body: CreateUserInput }>('/', async (request, reply) => {
const result = CreateUserSchema.safeParse(request.body);
if (!result.success) {
return reply.status(400).send({
error: 'Validation failed',
details: result.error.flatten(),
});
}
const user = await createUser(result.data);
return reply.status(201).send(user);
});
// With JSON Schema (Fastify validates)
fastify.post<{ Body: CreateUserInput; Reply: UserResponse }>('/v2', {
schema: {
body: zodToJsonSchema(CreateUserSchema),
response: {
201: zodToJsonSchema(UserResponseSchema),
},
},
}, async (request, reply) => {
const user = await createUser(request.body);
return reply.status(201).send(user);
});
};
// src/config/env.ts
import { z } from 'zod';
const EnvSchema = z.object({
NODE_ENV: z.enum(['development', 'production', 'test']).default('development'),
PORT: z.coerce.number().default(3000),
DATABASE_URL: z.string().url(),
JWT_SECRET: z.string().min(32),
CORS_ORIGIN: z.string().url().optional(),
LOG_LEVEL: z.enum(['debug', 'info', 'warn', 'error']).default('info'),
});
export type Env = z.infer<typeof EnvSchema>;
function validateEnv(): Env {
const result = EnvSchema.safeParse(process.env);
if (!result.success) {
console.error('Invalid environment variables:');
console.error(result.error.flatten().fieldErrors);
process.exit(1);
}
return result.data;
}
export const env = validateEnv();
import { z, ZodError } from 'zod';
function parseOrThrow<T>(schema: z.ZodSchema<T>, data: unknown): T {
return schema.parse(data);
}
function parseOrNull<T>(schema: z.ZodSchema<T>, data: unknown): T | null {
const result = schema.safeParse(data);
return result.success ? result.data : null;
}
// Flatten errors for API response
function formatZodError(error: ZodError): Record<string, string[]> {
return error.flatten().fieldErrors as Record<string, string[]>;
}
// Usage
try {
const user = parseOrThrow(UserSchema, requestBody);
} catch (error) {
if (error instanceof ZodError) {
return { errors: formatZodError(error) };
}
throw error;
}
// Base schemas
const AddressSchema = z.object({
street: z.string(),
city: z.string(),
country: z.string(),
postalCode: z.string(),
});
const ContactSchema = z.object({
email: z.string().email(),
phone: z.string().optional(),
});
// Composed schema
const CustomerSchema = z.object({
id: z.string().uuid(),
name: z.string(),
contact: ContactSchema,
billingAddress: AddressSchema,
shippingAddress: AddressSchema.optional(),
});
z.infer<typeof Schema>safeParse for graceful errorsrefine for async validationszodToJsonSchema for OpenAPI/Swagger