Skip to content

Getting Started

Install

Install nest-zod and its required peer dependencies:

sh
npm install nest-zod zod @nestjs/common rxjs
sh
yarn add nest-zod zod @nestjs/common rxjs
sh
pnpm add nest-zod zod @nestjs/common rxjs
sh
bun add nest-zod zod @nestjs/common rxjs
sh
deno add npm:nest-zod npm:zod npm:@nestjs/common npm:rxjs

If you also want generated Swagger metadata:

sh
npm install @nestjs/swagger
sh
yarn add @nestjs/swagger
sh
pnpm add @nestjs/swagger
sh
bun add @nestjs/swagger
sh
deno add npm:@nestjs/swagger

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 POST body is parsed with Zod before create() runs
  • the GET :id path param is validated as a UUID
  • the page query 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 property
  • ZQuery('name', schema) documents a named query parameter, including object-shaped values
  • ZSerialize documents the route's effective success status instead of always forcing 200

If You Do Not Use Swagger

Change only the import:

ts
import { 
ZBody
,
ZParam
,
ZQuery
,
ZSerialize
} from 'nest-zod';

Everything else stays the same. See Runtime Only for that version.

Released under the MIT License.