Getting Started
nest-zod requires Node.js 22 or newer.
Install
Install nest-zod and its required peer dependencies:
sh
npm install nest-zod zod @nestjs/common rxjssh
yarn add nest-zod zod @nestjs/common rxjssh
pnpm add nest-zod zod @nestjs/common rxjssh
bun add nest-zod zod @nestjs/common rxjssh
deno add npm:nest-zod npm:zod npm:@nestjs/common npm:rxjsIf you also want generated Swagger metadata:
sh
npm install @nestjs/swaggersh
yarn add @nestjs/swaggersh
pnpm add @nestjs/swaggersh
bun add @nestjs/swaggersh
deno add npm:@nestjs/swaggerAI Skills
This repo also includes AI skills for runtime-only and Swagger-aware nest-zod workflows. See AI Skills for installation and usage.
First Controller Example
If your app already uses Swagger, start with nest-zod/swagger:
ts
// items.schemas.ts
import { z } from 'zod';
export const createItemSchema = z.object({
title: z.string().trim().min(1),
});
export const listItemsQuerySchema = z.object({
page: z.coerce.number().int().positive().default(1),
});
export const itemResponseSchema = z.object({
id: z.uuid(),
title: z.string(),
});
export type CreateItemDto = z.infer<typeof createItemSchema>;
export type ListItemsQueryDto = z.infer<typeof listItemsQuerySchema>;
export type ItemResponseDto = z.infer<typeof itemResponseSchema>;ts
// items.controller.ts
import { Controller, Get, Post } from '@nestjs/common';
import { z } from 'zod';
import { ZBody, ZParam, ZQuery, ZSerialize } from 'nest-zod/swagger';
import type {
CreateItemDto,
ItemResponseDto,
ListItemsQueryDto,
} from './items.schemas';
import {
createItemSchema,
itemResponseSchema,
listItemsQuerySchema,
} from './items.schemas';
@Controller('items')
export class ItemsController {
@Post()
@ZSerialize(itemResponseSchema)
create(@ZBody(createItemSchema) body: CreateItemDto): ItemResponseDto {
return {
id: '550e8400-e29b-41d4-a716-446655440000',
title: body.title,
};
}
@Get(':id')
@ZSerialize(itemResponseSchema)
get(@ZParam('id', z.uuid()) id: string): ItemResponseDto {
return {
id,
title: 'Widget',
};
}
@Get()
list(@ZQuery(listItemsQuerySchema) query: ListItemsQueryDto) {
return {
page: query.page,
items: [],
};
}
}No Global Registration Needed
The decorators wire themselves up directly, so you do not need to register a global pipe or interceptor for this to work.
What Happens at Runtime
- the
POSTbody is parsed with Zod beforecreate()runs - the
GET :idpath param is validated as a UUID - the
pagequery string is coerced from a string into a positive integer - the response is passed through the schema before Nest sends it
When you use nest-zod/swagger:
ZQuery(schema)documents one query parameter per object propertyZQuery('name', schema)documents a named query parameter, including object-shaped valuesZSerializedocuments the route's effective success status instead of always forcing200
For async schemas, custom validation errors, supported peer versions, and OpenAPI boundaries, continue to Compatibility and Limits.
If You Do Not Use Swagger
Change only the import:
ts
import { , , , } from 'nest-zod';Everything else stays the same. See Runtime Only for that version.