Aviary
Validation

Pluggable Validation

zod, valibot, or arktype from one neutral schema IR.

nestjs-codegen translates your @Body()/@Query() DTOs (class-validator decorators) and defineContract schemas into a neutral SchemaNode IR, then renders that IR with a validation adapter. zod, valibot, and arktype each ship as their own adapter package (none is bundled in core). Adding a new library is one render function away (the interface is shaped around Standard Schema).

Choosing an adapter

import { defineConfig } from '@dudousxd/nestjs-codegen';
import { zodAdapter } from '@dudousxd/nestjs-codegen-zod';

export default defineConfig({
  validation: zodAdapter,
});

What gets generated

Given this DTO:

class CreateUserDto {
  @IsEmail() email!: string;
  @MinLength(8) password!: string;
}

each adapter renders forms.ts in its own idiom:

import { z } from 'zod';

export const CreateBodySchema = z.object({
  email: z.string().email(),
  password: z.string().min(8),
});
export type CreateBody = z.infer<typeof CreateBodySchema>;

Request types from schemas

The adapter decides how forms.ts is written. The types of the generated client come from wherever the route declares its schema — a defineContract, or a validation pipe such as @Body(new ZodPipe(schema)) — and are inferred by the TypeScript checker through Standard Schema, so refinements, defaults and transforms resolve to real types whichever library the schema is written in. See Schema pipes.

Decorator coverage

The class-validator → IR translator understands the common decorators: @IsString, @IsNumber/@IsInt, @IsBoolean, @IsDate, @IsEmail/@IsUrl/@IsUUID, @Matches, @MinLength/@MaxLength/@Length, @Min/@Max, @IsPositive/ @IsNegative, @IsEnum/@IsIn, @IsOptional/@IsDefined, @IsNotEmpty, @ValidateNested + @Type(() => X) (nested + arrays), @IsObject, @Allow.

Unmappable decorators are kept as a comment with a warning — the schema still compiles.

defineContract schemas are hand-written zod. They render verbatim under the zod adapter; under valibot/arktype they're skipped with a warning (use class-validator DTOs for cross-adapter forms, or the zod adapter for contracts).

Writing your own adapter

A ValidationAdapter renders the IR to source text:

import type { ValidationAdapter } from '@dudousxd/nestjs-codegen';

export const myAdapter: ValidationAdapter = {
  name: 'my-lib',
  importStatements: ({ used }) => (used ? ["import * as m from 'my-lib';"] : []),
  render(node, ctx) { /* SchemaNode → string */ return '…'; },
  renderModule(mod) { /* root + hoisted named schemas */ return { schemaText: '…', namedNestedSchemas: new Map(), warnings: [] }; },
  inferType: (constName) => `m.Infer<typeof ${constName}>`,
};

Pass the instance to validation and you're done.

On this page