Validation
Schemas validate the request and type the handler.
Suku validates through the Standard Schema interface. Zod, Valibot, ArkType and Effect Schema all implement it, so they work without adapters and Suku depends on none of them.
Give a route a schema for any part of the request.
import { z } from 'zod';
const CreateUser = z.object({
name: z.string().min(2),
email: z.email(),
});
route.post('/users', {
body: CreateUser,
services: { users: UsersService },
handle: ({ body, users }) => users.create(body), // body is { name: string; email: string }
});The handler is typed from the schema's output. There is no second type to keep in sync.
Invalid input never reaches the handler.
{
"statusCode": 400,
"message": "Validation failed for body",
"error": "Bad Request",
"issues": [
{ "path": "name", "message": "Too small: expected string to have >=2 characters" },
{ "path": "email", "message": "Invalid email address" }
]
}Nested paths are joined with dots, for example address.zip.
Params, query and headers
These arrive as strings, so use your library's coercion.
route.get('/users/:id', {
params: z.object({ id: z.coerce.number().int().positive() }),
query: z.object({ expand: z.enum(['posts', 'none']).default('none') }),
headers: z.object({ 'x-tenant': z.string() }),
handle: ({ params, query, headers }) => load(params.id, query.expand, headers['x-tenant']),
});Defaults and transforms are applied, because the handler receives the schema's output.
A params schema must cover every parameter named in the path, which is checked at
compile time.
Responses
A response schema checks what the handler returns and strips fields it does not declare.
const PublicUser = z.object({ id: z.number(), name: z.string() });
route.get('/users/:id', {
response: PublicUser,
handle: () => db.users.find(1), // passwordHash never leaves the server
});A return value that does not match is a server bug: it is logged and the client gets a 500.
Other libraries
import { z } from 'zod';
const CreateUser = z.object({ name: z.string().min(2) });All three go straight into body: CreateUser.
Outside a route
import { type Infer, validate } from '@sukujs/core';
type CreateUser = Infer<typeof CreateUser>;
const user = await validate(CreateUser, input); // throws ValidationExceptionValidationException extends BadRequestException and carries issues, which an
error handler can reshape.