Advanced Validation
import { ZInfer } from ‘@yelix/zod-validator’;
Header Validation
Section titled “Header Validation”Validate HTTP headers using the 'header' source:
import { YelixHono } from '@yelix/hono';import { zValidatorYelix } from '@yelix/zod-validator';import { z } from 'zod';
const app = new YelixHono();
const headerSchema = z.object({ 'x-api-key': z.string().min(32), 'x-request-id': z.string().uuid().optional(),});
app.get( '/protected', zValidatorYelix('header', headerSchema), (c) => { // ZInfer provides full type safety const headers: ZInfer<typeof headerSchema> = c.req.valid('header' as never); return c.json({ message: 'Access granted' }); });Cookie Validation
Section titled “Cookie Validation”Validate HTTP cookies using the 'cookie' source:
const cookieSchema = z.object({ sessionId: z.string().uuid(), theme: z.enum(['light', 'dark']).optional(),});
app.get( '/profile', zValidatorYelix('cookie', cookieSchema), (c) => { // ZInfer provides full type safety const cookies: ZInfer<typeof cookieSchema> = c.req.valid('cookie' as never); return c.json({ sessionId: cookies.sessionId }); });Form Data Validation
Section titled “Form Data Validation”Validate form data (multipart/form-data or application/x-www-form-urlencoded) using the 'form' source:
const formSchema = z.object({ title: z.string().min(1), description: z.string().optional(), category: z.enum(['image', 'video', 'document']),});
app.post( '/upload', zValidatorYelix('form', formSchema), async (c) => { // ZInfer provides full type safety const formData: ZInfer<typeof formSchema> = c.req.valid('form' as never); // Handle file uploads separately return c.json({ message: 'Upload successful', data: formData }); });Multiple Validations
Section titled “Multiple Validations”You can combine multiple validators in a single route:
app.post( '/users/:id/posts', // Validate path parameter zValidatorYelix('param', z.object({ id: z.string().uuid(), })), // Validate query parameters zValidatorYelix('query', z.object({ draft: z.string().transform((v) => v === 'true').optional(), })), // Validate JSON body zValidatorYelix('json', z.object({ title: z.string().min(1), content: z.string().min(10), tags: z.array(z.string()).optional(), })), // Validate headers zValidatorYelix('header', z.object({ 'x-user-id': z.string().uuid(), })), (c) => { // ZInfer provides full type safety for all validated data const params: ZInfer<typeof z.object({ id: z.string().uuid() })> = c.req.valid('param' as never); const query: ZInfer<typeof z.object({ draft: z.string().transform((v) => v === 'true').optional() })> = c.req.valid('query' as never); const body: ZInfer<typeof z.object({ title: z.string().min(1), content: z.string().min(10), tags: z.array(z.string()).optional() })> = c.req.valid('json' as never); const headers: ZInfer<typeof z.object({ 'x-user-id': z.string().uuid() })> = c.req.valid('header' as never);
return c.json({ params, query, body, headers }); });Custom Validation Messages
Section titled “Custom Validation Messages”Add custom error messages to your schemas:
const schema = z.object({ email: z.string().email('Invalid email format'), age: z.number() .int('Age must be an integer') .positive('Age must be positive') .min(18, 'Must be at least 18 years old') .max(120, 'Age must be less than 120'), password: z.string() .min(8, 'Password must be at least 8 characters') .regex(/[A-Z]/, 'Password must contain at least one uppercase letter') .regex(/[0-9]/, 'Password must contain at least one number'),});Refinements and SuperRefines
Section titled “Refinements and SuperRefines”Use Zod’s refine and superRefine for complex validation logic:
// Simple refinementconst passwordSchema = z.object({ password: z.string().min(8), confirmPassword: z.string(),}).refine( (data) => data.password === data.confirmPassword, { message: "Passwords don't match", path: ['confirmPassword'], });
// Super refine for multiple validationsconst userSchema = z.object({ email: z.string().email(), age: z.number(), startDate: z.string().datetime(), endDate: z.string().datetime(),}).superRefine((data, ctx) => { if (data.age < 18) { ctx.addIssue({ code: z.ZodIssueCode.custom, message: 'Must be 18 or older', path: ['age'], }); }
if (new Date(data.endDate) <= new Date(data.startDate)) { ctx.addIssue({ code: z.ZodIssueCode.custom, message: 'End date must be after start date', path: ['endDate'], }); }});Conditional Validation
Section titled “Conditional Validation”Use Zod’s conditional logic for dynamic validation:
const userSchema = z.object({ type: z.enum(['individual', 'company']), name: z.string(), email: z.string().email(), // Conditional fields based on type taxId: z.string().optional(), companyName: z.string().optional(),}).refine( (data) => { if (data.type === 'company' && !data.companyName) { return false; } if (data.type === 'individual' && !data.taxId) { return false; } return true; }, { message: 'Company name required for company type, tax ID required for individual', });Transformations
Section titled “Transformations”Transform data during validation:
const schema = z.object({ // Trim whitespace name: z.string().trim().min(1),
// Convert to lowercase email: z.string().email().toLowerCase(),
// Parse and validate date birthDate: z.string().datetime().transform((str) => new Date(str)),
// Convert string array to number array scores: z.array(z.string()) .transform((arr) => arr.map(Number)) .pipe(z.array(z.number().int().positive())),});Error Handling
Section titled “Error Handling”Yelix automatically handles validation errors, but you can customize the error response:
app.onError((err, c) => { if (err instanceof z.ZodError) { return c.json({ success: false, error: { message: 'Validation failed', issues: err.issues, }, }, 400); } return c.json({ error: 'Internal server error' }, 500);});Best Practices
Section titled “Best Practices”-
Use descriptive schema names: Name your schemas clearly
const createUserSchema = z.object({ ... });const updateUserSchema = z.object({ ... }); -
Reuse common schemas: Create reusable schema components
const emailSchema = z.string().email();const uuidSchema = z.string().uuid();const userSchema = z.object({id: uuidSchema,email: emailSchema,}); -
Validate early: Place validators before business logic
app.post('/users',zValidatorYelix('json', schema), // Validation firstauthenticate, // Then authhandler // Then business logic); -
Document with OpenAPI: Always use
openapi()middleware with validatorsapp.post('/users',openapi({ summary: 'Create User' }),zValidatorYelix('json', schema),handler);