Skip to content

JSON Body Validation

Validate JSON request bodies using zValidatorYelix with the 'json' source:

import { YelixHono } from '@yelix/hono';
import { zValidatorYelix, type ZInfer } from '@yelix/zod-validator';
import { z } from 'zod';
const app = new YelixHono();
app.post(
'/users',
zValidatorYelix('json', z.object({
name: z.string(),
email: z.email(),
age: z.number().int().positive(),
})),
(c) => {
// ZInfer provides full type safety
const { name, email, age }: ZInfer<typeof z.object({
name: z.string(),
email: z.email(),
age: z.number().int().positive(),
})> = c.req.valid('json' as never);
return c.json({ message: 'User created', name, email, age });
}
);

After validation, access the validated data using c.req.valid(). For type safety, use the ZInfer helper type:

import { zValidatorYelix, type ZInfer } from '@yelix/zod-validator';
import { z } from 'zod';
const userSchema = z.object({
name: z.string(),
email: z.email(),
age: z.number().int().positive(),
});
app.post('/users', zValidatorYelix('json', userSchema), (c) => {
// ZInfer provides full type safety - TypeScript knows the exact shape!
const userData: ZInfer<typeof userSchema> = c.req.valid('json' as never);
// userData is properly typed as: { name: string; email: string; age: number }
return c.json({ user: userData });
});

Note: The as never is needed due to TypeScript limitations with middleware type inference, but ZInfer ensures your data is fully type-safe, not any.

You can use all Zod features for complex validation:

import { z } from 'zod';
// Nested objects
const userSchema = z.object({
name: z.string().min(1),
email: z.email(),
address: z.object({
street: z.string(),
city: z.string(),
zipCode: z.string().regex(/^\d{5}$/),
}),
tags: z.array(z.string()),
});
app.post('/users', zValidatorYelix('json', userSchema), handler);
// Optional fields
const updateSchema = z.object({
name: z.string().optional(),
email: z.email().optional(),
age: z.number().int().positive().optional(),
});
app.patch('/users/:id', zValidatorYelix('json', updateSchema), handler);
// Union types
const responseSchema = z.union([
z.object({ type: 'success', data: z.any() }),
z.object({ type: 'error', message: z.string() }),
]);

Use Zod’s validation methods to add constraints:

const schema = z.object({
// String validations
name: z.string().min(1).max(100),
email: z.string().email(),
password: z.string().min(8).regex(/[A-Z]/).regex(/[0-9]/),
// Number validations
age: z.number().int().positive().max(120),
price: z.number().positive(),
// Array validations
tags: z.array(z.string()).min(1).max(10),
// Date validations
birthDate: z.string().datetime(),
// Custom validations
username: z.string().refine(
(val) => !val.includes(' '),
{ message: 'Username cannot contain spaces' }
),
});

When validation fails, Yelix automatically returns a 400 Bad Request response with error details:

// If validation fails, the client receives:
// Status: 400 Bad Request
// Body: {
// "success": false,
// "error": {
// "issues": [
// {
// "code": "invalid_type",
// "expected": "string",
// "received": "number",
// "path": ["email"],
// "message": "Expected string, received number"
// }
// ]
// }
// }

When used with Yelix’s OpenAPI system, the schema is automatically documented:

import { YelixHono, openapi } from '@yelix/hono';
import { zValidatorYelix } from '@yelix/zod-validator';
import { z } from 'zod';
const app = new YelixHono();
app.post(
'/users',
openapi({
summary: 'Create User',
description: 'Creates a new user account',
}),
zValidatorYelix('json', z.object({
name: z.string().min(1),
email: z.email(),
age: z.number().int().positive(),
})),
(c) => {
// ZInfer provides full type safety
const data: ZInfer<typeof z.object({
name: z.string().min(1),
email: z.email(),
age: z.number().int().positive(),
})> = c.req.valid('json' as never);
return c.json({ message: 'User created', data });
}
);

The request body schema will automatically appear in your OpenAPI documentation!