Validation with class-validator
Since Vovk.ts was heavily inspired by NestJSΒ it also supports class-validatorΒ library for server-side and client-side validation.
npm install vovk-dto
UserController.ts
import { prefix, post, openapi } from 'vovk';
import { withDto } from 'vovk-dto';
import { UpdateUserBodyDto, UpdateUserParamsDto, UpdateUserQueryDto, UpdateUserResponseDto } from './UserDto';
@prefix('users')
export default class UserController {
@openapi({
summary: 'Update user',
description: 'Update user by ID with Dto validation',
})
@post('{id}')
static updateUser = withDto({
body: UpdateUserBodyDto,
params: UpdateUserParamsDto,
query: UpdateUserQueryDto,
output: UpdateUserResponseDto,
async handle(req) {
const body = await req.vovk.body();
const query = req.vovk.query();
const params = req.vovk.params();
console.log(body instanceof UpdateUserBodyDto); // true
console.log(query instanceof UpdateUserQueryDto); // true
console.log(params instanceof UpdateUserParamsDto); // true
},
});
}
As you can see, the DTOs are decorated with JSONSchema
decorator to describe the properties of the DTOs when theyβre converted to JSON schemas. vovk-dto uses targetConstructorToSchema
function from class-validator-jsonschema to convert the DTOs to JSON schemas in order to be used for OpenAPI specification, Codegen and Function Calling.
Also notice that in the example above the req.vovk
object is used to access the validated input data. The req.vovk.body()
, req.vovk.query()
and req.vovk.params()
methods return the validated data as instances of the corresponding DTO classes. The req.json()
, req.nextUrl.searchParams
and the 2nd argument of the handle
function are still relevant ways to access the raw data.
Client-side validation with DTOs
As class-validator-jsonschemaΒ package allows to convert DTOs to JSON schemas, the common way of client-side validation with vovk-ajv may have various restrictions as the conversion to JSON schema is limitedΒ .
For the most robust solution itβs recommended to use vovk-dto/validateOnClient module that exports validateOnClient
function that can be used as an import in the vovk.config file:
/** @type {import('vovk').VovkConfig} */
const config = {
imports: {
validateOnClient: 'vovk-dto/validateOnClient',
},
};
export default config;
This will change the validateOnClient
import at the generated code:
import { validateOnClient } from 'vovk-dto/validateOnClient';
// ...
In order to perform the client-side validation, the input data should be transformed to the corresponding DTO class with class-transformerΒ . If input data is a plain object, or FormData
it will not be validated.
import { UserRPC } from 'vovk-client';
import { plainToInstance } from 'class-transformer';
import { UpdateUserBodyDto, UpdateUserResponseDto } from '@/modules/user/UserDto';
const respData = await UserRPC.updateUser({
body: plainToInstance(UpdateUserBodyDto, {
name: 'John Doe',
age: 42,
} satisfies UpdateUserBodyDto),
// ... same for query and params
});
// optionally transform response data to DTO
const dtoInstance = plainToInstance(UpdateUserResponseDto, respData);
In case if the data is neither a DTO class nor a plain object, nor FormData
, the validation can be skipped with disableClientValidation
.
import { UserRPC } from 'vovk-client';
await UserRPC.updateUser({
body: nonDtoInstance, // not a DTO class, not plain object nor FormData
disableClientValidation: true,
});
dto-mapped-types
In order to be able to apply pick, omit, partial and intersection patterns to DTOs, you can use dto-mapped-typesΒ package that is forked from @nestjs/mapped-typesΒ but doesnβt have unnecessary dependencies. It provides the same functionality as the original package but without the need to install @nestjs/common package.
import { IsString, IsEmail, MinLength, MaxLength, IsUUID } from 'class-validator';
import { OmitType } from 'dto-mapped-types';
@JSONSchema({
description: 'User DTO',
})
export class UserDto {
@IsUUID(4, { message: 'Invalid uuid format' })
id: string;
@IsString({ message: 'Name must be a string' })
@MinLength(2, { message: 'Name must be at least 2 characters' })
@MaxLength(20, { message: 'Name must be at most 20 characters' })
name: string;
@IsEmail({}, { message: 'Invalid email format' })
email: string;
}
@JSONSchema({
description: 'Create User DTO',
})
export class CreateUserDto extends OmitType(UserDto, ['id']) {
@IsString({ message: 'Password must be a string' })
password: string;
}