Validation
Hedystia ships with a built-in schema builder called h. It implements the Standard Schema specification, meaning it integrates cleanly with any Standard Schema-compatible library.
Importing h
import { h } from 'hedystia'
// or from the standalone package:
// import { h } from '@hedystia/validations'Primitive Types
h.string()
Validates a string value.
import { h } from 'hedystia'
h.string() // any string
h.string().minLength(3) // at least 3 characters
h.string().maxLength(100) // at most 100 characters
h.string().email() // valid email format
h.string().uuid() // valid UUID format
h.string().domain() // valid URL with http(s)
h.string().phone() // valid phone number
h.string().date() // parseable date string
h.string().regex(/^[a-z]+$/) // matches regexh.number()
Validates a number value.
import { h } from 'hedystia'
h.number() // any number
h.number().min(0) // must be >= 0
h.number().max(100) // must be <= 100
h.number().coerce() // coerce string → number (useful for URL params/query)h.boolean()
Validates a boolean value.
import { h } from 'hedystia'
h.boolean() // true or false
h.boolean().coerce() // coerce strings: "true"/"1" → true, "false"/"0" → falseh.any()
Accepts any value without validation.
import { h } from 'hedystia'
h.any()Composite Types
h.object()
Validates a plain object with typed fields.
import { h } from 'hedystia'
h.object({
id: h.number(),
name: h.string(),
email: h.string().email(),
})Nested objects are supported:
import { h } from 'hedystia'
h.object({
user: h.object({
id: h.number(),
profile: h.object({
bio: h.string(),
}),
}),
})h.literal()
Validates an exact value.
import { h } from 'hedystia'
h.literal('admin')
h.literal(42)
h.literal(true)h.options()
Validates a union of values. Pass multiple schemas; the first match wins.
import { h } from 'hedystia'
h.options(h.literal('en'), h.literal('es'), h.literal('fr'))
// equivalent to: 'en' | 'es' | 'fr'Array Schemas
Call .array() on any schema to validate an array of that type.
import { h } from 'hedystia'
h.string().array() // string[]
h.number().array() // number[]
h.object({ id: h.number() }).array() // Array<{ id: number }>.optional()
Makes any field optional (accepts undefined).
import { h } from 'hedystia'
h.object({
name: h.string(),
bio: h.string().optional(), // string | undefined
age: h.number().optional(),
}).null()
Extends a schema to also accept null.
import { h } from 'hedystia'
h.string().null() // string | null
h.number().null() // number | null
h.string().enum(['red', 'green', 'blue'] as const)
h.number().enum([1, 2, 3] as const)Using Schemas in Routes
All schemas are plugged into the route's third argument:
app.post(
'/products',
({ body }) => {
return { id: 1, ...body }
},
{
body: h.object({
name: h.string().minLength(1).maxLength(100),
price: h.number().min(0),
category: h.options(
h.literal('clothing'),
h.literal('electronics'),
h.literal('food')
),
tags: h.string().array().optional(),
}),
response: h.object({
id: h.number(),
name: h.string(),
price: h.number(),
}),
error: h.object({
message: h.string(),
code: h.number(),
}),
}
)Coercion
URL parameters and query strings are always strings. Use .coerce() to convert them automatically:
app.get(
'/items/:id',
({ params, query }) => {
// params.id is number (coerced)
// query.page is number (coerced)
return { id: params.id, page: query.page }
},
{
params: h.object({ id: h.number().coerce() }),
query: h.object({
page: h.number().coerce().optional(),
}),
}
)Standard Schema Compatibility
Since h implements the Standard Schema specification, you can use third-party libraries like Zod or Valibot in any schema position:
import { z } from 'zod'
app.post(
'/login',
({ body }) => ({ token: 'abc' }),
{
body: z.object({
username: z.string().min(3),
password: z.string().min(8),
}),
}
)