Nest

How to use Zod with NestJS pipes for runtime schemas?

March 18, 2026

download ready
Thank You
Your submission has been received.
We will be in touch and contact you soon!

NestJS integrates Zod via custom pipes that parse/validate incoming data against schemas at runtime, providing client-bundleable validation separate from TypeScript types. Create ZodValidationPipe using zod.toParsedSchema() for transformation; ZodPipe handles safeParse/coercion. Apply via @Body(new ZodPipe(schema)) in controllers for automatic validation with detailed ZodError responses. Combines inference (z.infer<typeof schema>) for typesafety with tree-shakable bundles; outperforms class-validator for frontend reuse. Supports async parsing and custom error maps for i18n.

Example:

Step 1: Install & Schema

Code

npm i zod class-transformer @nestjs/class-validator
      

Step 2: ZodPipe (Custom Pipe)

Code

// pipes/zod.pipe.ts
import { PipeTransform, Injectable, ArgumentMetadata, BadRequestException } from '@nestjs/common';
import { ZodSchema, ZodError } from 'zod';
import { plainToClass } from 'class-transformer';

@Injectable()
export class ZodPipe<T> implements PipeTransform {
  constructor(private schema: ZodSchema<T>) {}

  transform(value: any, metadata: ArgumentMetadata) {
    const result = this.schema.safeParse(value);
    if (!result.success) {
      throw new BadRequestException('Validation failed', result.error.flatten());
    }
    return plainToClass(Object, result.data);  // Optional: DTO transform
  }
}
      

Step 3: Controller Usage

Code

// dto/create-user.dto.ts
import { z } from 'zod';
export const createUserSchema = z.object({
  email: z.string().email('Invalid email'),
  name: z.string().min(2).max(50),
  age: z.coerce.number().min(18)
});
export type CreateUserDto = z.infer<typeof createUserSchema>;

// user.controller.ts
import { Body, Post, UsePipes } from '@nestjs/common';
import { ZodPipe } from '../pipes/zod.pipe';
import { createUserSchema, CreateUserDto } from './dto/create-user.dto';

@Controller('users')
export class UserController {
  @Post()
  @UsePipes(new ZodPipe(createUserSchema))
  create(@Body() body: CreateUserDto) {  // Typed automatically
    return { id: 1, ...body };
  }
}
      
Hire Now!

Need Help with Nest Development ?

Work with our skilled nest developers to accelerate your project and boost its performance.
**Hire now**Hire Now**Hire Now**Hire now**Hire now

How to use Zod with NestJS pipes for runtime schemas?

NestJS integrates Zod via custom pipes that parse/validate incoming data against schemas at runtime, providing client-bundleable validation separate from TypeScript types. Create ZodValidationPipe using zod.toParsedSchema() for transformation; ZodPipe handles safeParse/coercion. Apply via @Body(new ZodPipe(schema)) in controllers for automatic validation with detailed ZodError responses. Combines inference (z.infer<typeof schema>) for typesafety with tree-shakable bundles; outperforms class-validator for frontend reuse. Supports async parsing and custom error maps for i18n.

Example:

Step 1: Install & Schema

Code

npm i zod class-transformer @nestjs/class-validator
      

Step 2: ZodPipe (Custom Pipe)

Code

// pipes/zod.pipe.ts
import { PipeTransform, Injectable, ArgumentMetadata, BadRequestException } from '@nestjs/common';
import { ZodSchema, ZodError } from 'zod';
import { plainToClass } from 'class-transformer';

@Injectable()
export class ZodPipe<T> implements PipeTransform {
  constructor(private schema: ZodSchema<T>) {}

  transform(value: any, metadata: ArgumentMetadata) {
    const result = this.schema.safeParse(value);
    if (!result.success) {
      throw new BadRequestException('Validation failed', result.error.flatten());
    }
    return plainToClass(Object, result.data);  // Optional: DTO transform
  }
}
      

Step 3: Controller Usage

Code

// dto/create-user.dto.ts
import { z } from 'zod';
export const createUserSchema = z.object({
  email: z.string().email('Invalid email'),
  name: z.string().min(2).max(50),
  age: z.coerce.number().min(18)
});
export type CreateUserDto = z.infer<typeof createUserSchema>;

// user.controller.ts
import { Body, Post, UsePipes } from '@nestjs/common';
import { ZodPipe } from '../pipes/zod.pipe';
import { createUserSchema, CreateUserDto } from './dto/create-user.dto';

@Controller('users')
export class UserController {
  @Post()
  @UsePipes(new ZodPipe(createUserSchema))
  create(@Body() body: CreateUserDto) {  // Typed automatically
    return { id: 1, ...body };
  }
}