This library provides helper utilities for writing and validating NestJS APIs using [TypeBox](https://github.com/sinclairzx81/typebox) as an alternative to class-validator/class-transformer. Can be configured to patch @nestjs/swagger allowing OpenAPI gene
npm install nestjs-typeboxThis library provides helper utilities for writing and validating NestJS APIs using TypeBox as
an alternative to class-validator/class-transformer. Can be configured to patch @nestjs/swagger allowing OpenAPI generation to continue working.
Supports property defaults, basic type coercion, transforms, stripping unknown properties, and custom error messages. See typebox docs for more info.
``sh`
npm i nestjs-typebox @sinclair/typebox
> Important: Note that nestjs-typebox is an alternative to the class-validator DTO approach detailed in the NestJS docs, and is
> meant to fully replace it and all of the built-in validation/parsing pipes. Make sure you remove any global validation/parsing pipes
> before installing this library and avoid using any local validation/parsing pipe decorators in combination with this library's decorators.
> The example below demonstrates a discriminated union type,
> which cannot be achieved using class-based introspection approaches like that of class-validator.
`ts
import { Type } from '@sinclair/typebox';
export const PetSchemaBase = Type.Object({
id: Type.Number(),
name: Type.String({
description: "The pet's name",
examples: ['Figaro'],
}),
microchip: Type.String(){
minLength: 10,
description: 'Secret microchip number. Not sent to client',
errorMessage: '"microchip" is required and must be at least 10 characters.'
},
});
export const CatSchema = Type.Composite([
PetSchemaBase,
Type.Object({
type: Type.Literal('cat'),
breed: Type.Union([Type.Literal('shorthair'), Type.Literal('persian'), Type.Literal('siamese')]),
}),
]);
export const DogSchema = Type.Composite([
PetSchemaBase,
Type.Object({
type: Type.Literal('dog'),
breed: Type.Union([Type.Literal('shiba-inu'), Type.Literal('poodle'), Type.Literal('dachshund')]),
}),
]);
export const PetSchema = Type.Union([CatSchema, DogSchema]);
export type Pet = Static
`
> The example below shows two different decorators and their usage, calling out default configuration.
> Schemas have all been defined inline for brevity, but could just as easily be defined elsewhere
> and reused. The primary benefit of using @HttpEndpoint over @Validator is the additional validation
> enforcing path parameters to be properly defined as request "param" validators. Otherwise, it simply
> passes through options specified in validate to the underlying @Validator decorator.
`ts
import { Type } from '@sinclair/typebox';
import { Validate, HttpEndpoint } from 'nestjs-typebox';
@Controller('pets')
export class PetController {
constructor(private readonly petService: PetService) {}
@Get()
@Validate({
response: { schema: Type.Array(Type.Omit(PetSchema, ['microchip'])), stripUnknownProps: true },
})
async getPets() {
return this.petService.getPets();
}
@Get(':id')
@Validate({
// stripUnknownProps is true by default for response validators
// so this shorthand is equivalent
response: Type.Omit(PetSchema, ['microchip']),
request: [
// coerceTypes is true by default for "param" and "query" request validators
{ name: 'id', type: 'param', schema: Type.Number(), coerceTypes: true },
],
})
// no need to use @Param() decorator here since the @Validate() decorator will
// automatically attach a pipe to populate and convert the paramater value
async getPet(id: number) {
return this.petService.getPet(id);
}
@Post()
@Validate({
response: Type.Omit(PetSchema, ['microchip']),
request: [
// if "name" not provided, method name will be used
{ type: 'body', schema: Type.Omit(PetSchema, 'id') },
],
})
async createPet(data: Omit
return this.petService.createPet(data);
}
@HttpEndpoint({
method: 'PATCH',
path: ':id',
validate: {
response: Type.Omit(PetSchema, ['microchip']),
request: [
{ name: 'id', type: 'param', schema: Type.Number() },
{ type: 'body', schema: Type.Partial(Type.Omit(PetSchema, ['id'])) },
],
},
})
// the order of the controller method parameters must correspond to the order/types of
// "request" validators, including "required" configuration. Additionally nestjs-typebox will
// throw at bootup if parameters defined in the "request" validator config don't correspond
// with the parameters defined in the "path" configuration
async updatePet(id: number, data: Partial
return this.petService.updatePet(id, data);
}
@HttpEndpoint({
method: 'DELETE',
path: ':id',
validate: {
response: Type.Omit(PetSchema, ['microchip']),
request: [{ name: 'id', type: 'param', schema: Type.Number() }],
},
})
async deletePet(id: number) {
return this.petService.deletePet(id);
}
}
`
Calling configure allows for the patching of the swagger plugin, custom
string formats (email, url, date, time, date-time, uuid), and support for errorMessage overrides
within schema options.
`ts
// main.ts
import { Reflector } from '@nestjs/core';
import { configureNestJsTypebox } from 'nestjs-typebox';
configureNestJsTypebox({
patchSwagger: true,
setFormats: true,
});
async function bootstrap() {
const app = await NestFactory.create(AppModule);
await app.listen(3000);
console.log(Application is running on: ${await app.getUrl()});
}
bootstrap();
``
Swagger patch derived from https://github.com/risenforces/nestjs-zod
- Validate observable support
- utility to create typebox schemas with CRUD defaults (i.e. SchemaName['response'], SchemaName['update'])
- include method name in decorator errors
- support validating entire query object? (instead of individual values)
- check controller metadata so resolved path can include params specified at the controller level