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.