Skip to content

Conversation

@malkovitc
Copy link

PR Checklist

PR Type

  • Bugfix
  • Feature

What is the current behavior?

Issue Number: #14539

NestJS currently relies on class-validator and class-transformer for validation, which requires decorators and runtime reflection. Many modern validation libraries (Zod, Valibot, ArkType, etc.) now support Standard Schema - a common interface for schema validation.

What is the new behavior?

Added StandardSchemaValidationPipe that works with any Standard Schema v1 compliant library:

import { z } from 'zod';

class CreateUserDto {
  static schema = z.object({
    email: z.string().email(),
    name: z.string().min(2),
  });

  email: string;
  name: string;
}

// Apply globally
app.useGlobalPipes(new StandardSchemaValidationPipe());

// Or per-route
@Post()
@UsePipes(new StandardSchemaValidationPipe())
create(@Body() dto: CreateUserDto) {
  return this.service.create(dto);
}

Features:

  • Works with any Standard Schema v1 compliant library (Zod, Valibot, ArkType, etc.)
  • Zero runtime dependencies - schemas are defined as static properties on DTO classes
  • Async validation support
  • Customizable error HTTP status code
  • Custom exception factory support
  • Configurable schema property name

Options:

Option Type Default Description
errorHttpStatusCode ErrorHttpStatusCode 400 HTTP status code for validation errors
exceptionFactory (issues) => any Default factory Custom exception factory
disableErrorMessages boolean false Hide error details in response
validateCustomDecorators boolean false Validate custom parameter decorators
schemaProperty string 'schema' Static property name for schema

Does this PR introduce a breaking change?

  • Yes
  • No

This is a new feature that doesn't affect existing ValidationPipe behavior. Users can gradually adopt it for new endpoints while continuing to use ValidationPipe for existing code.

@malkovitc
Copy link
Author

Added integration tests with real Zod schemas (10 tests):

  • Valid data creation
  • Optional fields handling
  • Invalid email rejection
  • Min length validation
  • Missing required fields
  • Invalid type rejection
  • Value range validation
  • Query parameters with coercion
  • disableErrorMessages option
  • Custom errorHttpStatusCode option

All tests pass with Zod v3.24.

@micalevisk
Copy link
Member

I found a bit odd that we need to define the same properties twice (in the class with no metadata & with zod)

At same time I don't think that we can address this without losing the class-like approach

@malkovitc
Copy link
Author

Yeah, you're right - there's definitely duplication here. It's a known trade-off when mixing Zod with classes.

A couple of options I see:

  1. Use z.infer + implements to at least keep types in sync:
export class CreateUserDto implements z.infer<typeof createUserSchema> {
  static schema = createUserSchema;
  declare name: string;
  declare email: string;
}
  1. Go schema-only without classes - but as you said, we lose the class-like approach

  2. Just accept the duplication - it's explicit and honestly not that different from repeating property names with class-validator decorators

Since this pipe is Standard Schema agnostic, users could also pick TypeBox or Valibot which infer types from schemas more naturally.

Want me to update the example to show the z.infer pattern? Or do you have a different approach in mind?

@micalevisk
Copy link
Member

Yes, let's stick with the implements approach then

@micalevisk
Copy link
Member

Please add an example with nested DTOs and with @nestjs/swagger

Copy link

@BenLorantfy BenLorantfy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @malkovitc @micalevisk, maintainer of nestjs-zod here 👋

I have a few questions about this PR and how 3rd party libraries like nestjs-zod fit into this. I'd like to:

  1. Make sure nestjs-zod is aligned with the work being done here
  2. Figure out how nestjs-zod fits into all this
  3. Understand if nestjs-zod is needed at all anymore (I think it is, based off my understanding of how zod has additional features built on-top of standard schema)

nestjs-zod has a decent number of users (1 million downloads per month), so I'd like to also figure out how to avoid disruption for them.

Also please note in this issue the possibility of moving nestjs-zod as an official package was brought up: BenLorantfy/nestjs-zod#65. It might be good to resolve this issue so nestjs-zod is aligned with the work being done here.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know this PR just adds a validation pipe, but I'm curious if there's a plan to add serialization and how that would work? Because I have a few questions about serialization:

  1. For zod at-least, consumers likely expect parse to run when serializing the response, so transform()s are run, extra fields are removed, etc.
  2. zod also has a concept of codecs, which I don't think are part of standard schema. If a consumer is using a codec, they likely expect encode to run when serializing out the response. You can see how nestjs-zod handles this here: https://github.com/BenLorantfy/nestjs-zod?tab=readme-ov-file#codecs

Is serialization going to be a responsibility of external libraries built ontop of this PR?

Comment on lines +97 to +106
export class QueryDto implements z.infer<typeof querySchema> {
static schema = querySchema;

@ApiPropertyOptional({
description: 'Number of items to return',
example: 10,
minimum: 1,
maximum: 100,
})
declare limit?: number;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So my biggest problem with class-validator is that it leads you to duplicate information. I wrote about this here: https://www.benlorantfy.com/blog/the-data-triangle-and-nestjs-zod-v5

Is there no way to avoid this? In nestjs-zod we avoid this by using a factory function:

image

I'm worried about not only the schema field names being duplicated by the class field names, but also other information like description (which might be set by meta({ description: '' }) on the schema) and minimum (which might be set by gt())

I'm also worried consumers might forget to add implements z.infer<typeof createUserSchema>, in which case the duplication is not just extra characters but also caries risk of the type information drifting.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the idea that other 3rd party libraries (like nestjs-zod) would build a factory function on-top of this work, to handle avoiding duplication like this?

Copy link

@BenLorantfy BenLorantfy Jan 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

zod also has a function called toJSONSchema that creates json schemas from zod schemas. This is how nestjs-zod is able to use description from meta({ description: '' }) without needing @ApiProperty

toJSONSchema also produces different results based off io: https://zod.dev/json-schema#io .

Is it up to 3rd party libraries, like nestjs-zod to implement _OPENAPI_METADATA_FACTORY ?

See: https://github.com/BenLorantfy/nestjs-zod/blob/61c59dde828b050f22e91a00bbde97024babd62f/packages/nestjs-zod/src/dto.ts#L60-L62

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there any plan here to make it so consumers can make reusable / named openapi schemas? See: https://github.com/BenLorantfy/nestjs-zod?tab=readme-ov-file#reusable-schemas

Is this going to be a responsibility of external libraries built onto of this PR?

@micalevisk
Copy link
Member

micalevisk commented Jan 5, 2026

@BenLorantfy hi! Well done!

Now we are more into supporting the standard rather than creating a @nestjs/zod

I'm still no sure how to properly support the standard but it seems to be really wanted by the nodejs community

@abenhamdine
Copy link

abenhamdine commented Jan 5, 2026

@BenLorantfy hi! Well done!

Now we are more into supporting the standard rather than creating a @nestjs/zod

I'm still no sure how to properly support the standard but it seems to be really wanted by the nodejs community

I think the 2 things have different scopes.
BentLorantfy/nestjs-zod brings additional features

Both could be considered

@0x0bit
Copy link

0x0bit commented Jan 7, 2026

I have a few questions about this PR and how 3rd party libraries like nestjs-zod fit into this. I'd like to:

  1. Make sure nestjs-zod is aligned with the work being done here
  2. Figure out how nestjs-zod fits into all this
  3. Understand if nestjs-zod is needed at all anymore (I think it is, based off my understanding of how zod has additional features built on-top of standard schema)

Why integrate nestjs-zod into the standard pattern? The very reason for pushing this issue is to eliminate these third-party packages. While nestjs-zod is excellent, like class-validator, its developers aren't particularly active in maintaining it. So no matter how rich or robust its features, issues won't be resolved promptly. In production environments, everyone must evaluate whether it's worth introducing. Moreover, the choice of validation approach should remain entirely up to developers—a matter of personal preference. Those seeking standardization can opt for the standard pattern, while those desiring richer functionality can choose nestjs-zod or class-validator. Crucially, nestjs-zod or class-validator should support the standard pattern, not the other way around.

@BenLorantfy
Copy link

BenLorantfy commented Jan 9, 2026

Hi @0x0bit , thanks for your reply

its developers aren't particularly active in maintaining it

I'm the maintainer and I'm a bit confused by this statement. nestjs-zod has minimal open issues and we just released 5.1.0. I don't really think it's fair to say it's not actively maintained.

Why integrate nestjs-zod into the standard pattern?

I'm not sure what you mean by this?

Crucially, nestjs-zod or class-validator should support the standard pattern, not the other way around.

I don't think I said it should be "the other way around" ? I think we're on the same page here.

The very reason for pushing this issue is to eliminate these third-party packages

those desiring richer functionality can choose nestjs-zod or class-validator

@0x0bit these statements are contradictory

Anyway, my questions were about figuring out how nestjs-zod relates to the work being done in this PR. I think the answer is nestjs-zod is still valuable and could be built on-top of the work being done here, because nestjs-zod handles:

  1. zod codecs
  2. serialization
  3. openapi generation without duplicated information
  4. openapi generation with named / reusable schemas

And I don't see any handling of these topics in this PR

evgeniy.chernomortsev added 3 commits January 9, 2026 12:19
Add a new validation pipe that supports Standard Schema-compliant validators
(Zod, Valibot, ArkType, etc.) for request validation.

Features:
- Works with any Standard Schema v1 compliant library
- Zero runtime dependencies - schemas are defined on DTO classes
- Async validation support
- Customizable error HTTP status code
- Custom exception factory support
- Configurable schema property name

Usage:
```typescript
import { z } from 'zod';

class CreateUserDto {
  static schema = z.object({
    email: z.string().email(),
    name: z.string().min(2),
  });

  email: string;
  name: string;
}

app.useGlobalPipes(new StandardSchemaValidationPipe());
```

Closes nestjs#14539
- Add Zod as devDependency for testing
- Create integration test suite with real Zod schemas
- Test valid/invalid data handling
- Test query parameters validation
- Test pipe options (disableErrorMessages, errorHttpStatusCode)
@malkovitc malkovitc force-pushed the feat/standard-schema-validation-pipe branch from 7893aed to 93b0ec1 Compare January 9, 2026 08:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants