Standard Schema Validation
Standard Schema is a convention supported by modern validation libraries that implement a common interface for defining and validating data structures. It is supported by libraries such as Zod 4 , ArkType , Valibot , and more. See the full list of implementations in the repository .
Vovk.ts provides a simple createStandardValidation function to build a custom validation library using Standard Schema. Its key option is toJSONSchema, which defines how to convert models to JSON Schema.
ArkType Example
Create withArk somewhere in your codebase:
import { createStandardValidation } from 'vovk';
import { type } from 'arktype';
export const withArk = createStandardValidation({
toJSONSchema: (model: type) => model.toJsonSchema({
fallback: {
proto: (ctx) => ctx.proto === File ? {
type: "string",
format: "binary",
} : ctx.base,
default: (ctx) => ctx.base
}
})
});Then use it in your controller:
import { prefix, post, operation } from 'vovk';
import { type } from 'arktype';
import { withArk } from '@/lib/withArk';
@prefix('users')
export default class UserController {
@operation({
summary: 'Update user (Arktype)',
description: 'Update user by ID with Arktype validation',
})
@post('{id}')
static updateUser = withArk({
body: type({
name: type('string').describe('User full name'),
age: type('0 < number < 120').describe('User age'),
email: type('string.email').describe('User email'),
}),
params: type({
id: type('string.uuid').describe('User ID'),
}),
query: type({
notify: type('"email" | "push" | "none"').describe('Notification type'),
}),
output: type({
success: type('boolean').describe('Success status'),
}),
async handle(req, { id }) {
const { name, age } = await req.json();
const notify = req.nextUrl.searchParams.get('notify');
// do something with the data
console.log(`Updating user ${id}:`, { name, age, notify });
return {
success: true,
};
},
});
}Valibot Example
Create withValibot somewhere in your codebase:
import { createStandardValidation } from 'vovk';
import { toJsonSchema } from '@valibot/to-json-schema';
export const withValibot = createStandardValidation({
toJSONSchema: (model) => toJsonSchema(model, {
overrideSchema(context) {
if (context.valibotSchema.type === 'file') {
return { type: 'string', format: 'binary' };
}
},
}),
});Then use it in your controller:
import { prefix, post, operation } from 'vovk';
import * as v from 'valibot';
import { withValibot } from '@/lib/withValibot';
@prefix('users')
export default class UserController {
@operation({
summary: 'Update user (Valibot)',
description: 'Update user by ID with Valibot validation',
})
@post('{id}')
static updateUser = withValibot({
body: v.pipe(
v.object({
name: v.pipe(v.string(), v.description('User full name')),
age: v.pipe(v.number(), v.minValue(0), v.maxValue(120), v.description('User age')),
email: v.pipe(v.string(), v.email(), v.description('User email')),
}),
v.description('User object')
),
params: v.object({
id: v.pipe(v.string(), v.uuid(), v.description('User ID')),
}),
query: v.object({
notify: v.pipe(v.picklist(['email', 'push', 'none']), v.description('Notification type')),
}),
output: v.pipe(
v.object({
success: v.pipe(v.boolean(), v.description('Success status')),
}),
v.description('Response object')
),
async handle(req, { id }) {
const { name, age } = await req.json();
const notify = req.nextUrl.searchParams.get('notify');
// do something with the data
console.log(`Updating user ${id}:`, { name, age, notify });
return {
success: true,
};
},
});
}Other libraries follow the same pattern; only the toJSONSchema implementation differs. Once standard-schema/issues/21 is resolved, using createStandardValidation will likely be unnecessary in most cases.