Skip to content

Advanced Validation

import { ZInfer } from ‘@yelix/zod-validator’;

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' });
}
);

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 });
}
);

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 });
}
);

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 });
}
);

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'),
});

Use Zod’s refine and superRefine for complex validation logic:

// Simple refinement
const 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 validations
const 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'],
});
}
});

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',
}
);

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())),
});

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);
});
  1. Use descriptive schema names: Name your schemas clearly

    const createUserSchema = z.object({ ... });
    const updateUserSchema = z.object({ ... });
  2. 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,
    });
  3. Validate early: Place validators before business logic

    app.post('/users',
    zValidatorYelix('json', schema), // Validation first
    authenticate, // Then auth
    handler // Then business logic
    );
  4. Document with OpenAPI: Always use openapi() middleware with validators

    app.post('/users',
    openapi({ summary: 'Create User' }),
    zValidatorYelix('json', schema),
    handler
    );