npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@mag123c/nestjs-stdschema

v0.2.2

Published

Universal schema validation for NestJS using standard-schema spec. Support Zod, Valibot, ArkType and 20+ validators with a single package.

Readme

@mag123c/nestjs-stdschema

Universal schema validation for NestJS using the standard-schema specification.

npm version CI codecov License: MIT

Why This Package?

  • One package, any standard-schema validator: Tested with Zod & Valibot, compatible with 20+ validators implementing the spec
  • Zero vendor lock-in: Switch validators without changing your NestJS code
  • Type-safe: Full TypeScript support with automatic type inference
  • OpenAPI ready: Automatic Swagger documentation via @nestjs/swagger integration
  • Minimal footprint: No runtime dependencies on specific validators

Installation

npm install @mag123c/nestjs-stdschema
# or
pnpm add @mag123c/nestjs-stdschema
# or
yarn add @mag123c/nestjs-stdschema

Then install your preferred validator:

# Zod
npm install zod

# Valibot
npm install valibot

# ArkType
npm install arktype

Quick Start

Basic Validation (Route Level)

import { Body, Controller, Post } from '@nestjs/common';
import { StandardValidationPipe } from '@mag123c/nestjs-stdschema';
import { z } from 'zod';

const CreateUserSchema = z.object({
  name: z.string().min(1),
  email: z.string().email(),
  age: z.number().optional(),
});

@Controller('users')
export class UsersController {
  @Post()
  create(
    @Body(new StandardValidationPipe(CreateUserSchema))
    body: z.infer<typeof CreateUserSchema>,
  ) {
    return body;
  }
}

With DTO Class

import { createStandardDto, StandardValidationPipe } from '@mag123c/nestjs-stdschema';
import { z } from 'zod';

const CreateUserSchema = z.object({
  name: z.string(),
  email: z.string().email(),
});

// Create a DTO class with automatic type inference
class CreateUserDto extends createStandardDto(CreateUserSchema) {}

@Controller('users')
export class UsersController {
  @Post()
  create(
    @Body(new StandardValidationPipe(CreateUserDto.schema))
    body: CreateUserDto,
  ) {
    // body is fully typed as { name: string; email: string }
    return body;
  }
}

Global Pipe

import { StandardValidationPipe } from '@mag123c/nestjs-stdschema';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  // Global pipe requires @Schema decorator or createStandardDto
  app.useGlobalPipes(new StandardValidationPipe());

  await app.listen(3000);
}

Important: Global pipe relies on TypeScript's design:paramtypes metadata to detect DTO classes. See Requirements for Global Pipe section.

With Valibot

import { StandardValidationPipe } from '@mag123c/nestjs-stdschema';
import * as v from 'valibot';

const CreateUserSchema = v.object({
  name: v.pipe(v.string(), v.minLength(1)),
  email: v.pipe(v.string(), v.email()),
});

@Post()
create(
  @Body(new StandardValidationPipe(CreateUserSchema))
  body: v.InferOutput<typeof CreateUserSchema>,
) {
  return body;
}

Response Serialization

Strip sensitive fields from responses using StandardSerializerInterceptor:

import {
  StandardSerializerInterceptor,
  ResponseSchema,
  createStandardDto,
} from '@mag123c/nestjs-stdschema';
import { z } from 'zod';

const UserResponseSchema = z.object({
  id: z.string(),
  name: z.string(),
  // email and password are excluded from schema
});

class UserResponseDto extends createStandardDto(UserResponseSchema) {}

@Controller('users')
@UseInterceptors(StandardSerializerInterceptor)
export class UsersController {
  @Get(':id')
  @ResponseSchema(UserResponseDto)
  findOne(@Param('id') id: string) {
    // Even if this returns { id, name, email, password },
    // only { id, name } will be sent to the client
    return this.userService.findOne(id);
  }

  @Get()
  @ResponseSchema([UserResponseDto]) // Array response
  findAll() {
    return this.userService.findAll();
  }
}

Global Interceptor

import { Reflector } from '@nestjs/core';
import { StandardSerializerInterceptor } from '@mag123c/nestjs-stdschema';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  app.useGlobalInterceptors(
    new StandardSerializerInterceptor(app.get(Reflector))
  );

  await app.listen(3000);
}

Note: The serializer strips extra fields by leveraging the validator's default behavior. Both Zod and Valibot strip unknown keys by default. If your validator preserves extra keys, use its strict/strip mode explicitly.

GraphQL Support

StandardValidationPipe works with @nestjs/graphql out of the box:

Route-Level Validation

import { Resolver, Mutation, Args } from '@nestjs/graphql';
import { StandardValidationPipe } from '@mag123c/nestjs-stdschema';
import { z } from 'zod';

const CreateUserSchema = z.object({
  name: z.string().min(1),
  email: z.string().email(),
});

@Resolver(() => User)
export class UserResolver {
  @Mutation(() => User)
  createUser(
    @Args('input', { type: () => CreateUserInput }, new StandardValidationPipe(CreateUserSchema))
    input: CreateUserInput,
  ) {
    return this.userService.create(input);
  }
}

Global Pipe with GraphQL

When using global pipe with GraphQL, set validateCustomDecorators: true because @Args() uses metadata.type === 'custom':

import { StandardValidationPipe } from '@mag123c/nestjs-stdschema';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  app.useGlobalPipes(
    new StandardValidationPipe({ validateCustomDecorators: true })
  );

  await app.listen(3000);
}

GraphQL Error Handling

GraphQL converts HttpException to GraphQL errors. For better error formatting, use Apollo's formatError:

import { GraphQLModule } from '@nestjs/graphql';
import { ApolloDriver, ApolloDriverConfig } from '@nestjs/apollo';

@Module({
  imports: [
    GraphQLModule.forRoot<ApolloDriverConfig>({
      driver: ApolloDriver,
      autoSchemaFile: true,
      formatError: (error) => {
        const originalError = error.extensions?.originalError as any;
        return {
          message: originalError?.message ?? error.message,
          code: error.extensions?.code ?? 'INTERNAL_SERVER_ERROR',
          errors: originalError?.errors ?? undefined,
        };
      },
    }),
  ],
})
export class AppModule {}

This produces cleaner error responses:

{
  "errors": [{
    "message": "Validation failed",
    "code": "BAD_REQUEST",
    "errors": [
      { "path": ["email"], "message": "Invalid email" }
    ]
  }]
}

API Reference

StandardValidationPipe

new StandardValidationPipe(schema?, options?)

Options:

| Option | Type | Default | Description | |--------|------|---------|-------------| | errorHttpStatusCode | HttpStatus | 400 | HTTP status code for validation errors | | exceptionFactory | (issues) => any | - | Custom exception factory | | validateCustomDecorators | boolean | false | Validate custom decorator parameters | | expectedType | Type<any> | - | Override metatype for validation |

createStandardDto

function createStandardDto<T extends StandardSchemaV1>(
  schema: T,
  options?: { openapi?: OpenAPIMetadata }
): StandardDtoClass<T>;

Creates a DTO class from a schema with:

  • Static schema property
  • Automatic type inference
  • OpenAPI metadata generation

Decorators

| Decorator | Description | |-----------|-------------| | @Schema(schema) | Attach schema to existing class | | @ResponseSchema(dto) | Define response schema for serialization | | @ResponseSchema([dto]) | Define array response schema |

Utilities

| Function | Description | |----------|-------------| | getSchema(target) | Get schema from DTO class | | schemaToOpenAPI(schema, metadata?) | Convert schema to OpenAPI format |

Type Utilities

import { InferInput, InferOutput } from '@mag123c/nestjs-stdschema';

type Input = InferInput<typeof MySchema>;   // Input type
type Output = InferOutput<typeof MySchema>; // Output type

Error Response Format

{
  "statusCode": 400,
  "message": "Validation failed",
  "errors": [
    {
      "path": ["email"],
      "message": "Invalid email"
    },
    {
      "path": ["age"],
      "message": "Expected number, received string"
    }
  ]
}

Custom Error Format

new StandardValidationPipe(schema, {
  errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY,
  exceptionFactory: (issues) => {
    return new UnprocessableEntityException({
      code: 'VALIDATION_ERROR',
      errors: issues.map(issue => ({
        field: issue.path?.join('.') ?? 'root',
        message: issue.message,
      })),
    });
  },
});

OpenAPI Integration

DTOs created with createStandardDto automatically work with @nestjs/swagger:

import { createStandardDto } from '@mag123c/nestjs-stdschema';
import { z } from 'zod';

const UserSchema = z.object({
  name: z.string(),
  email: z.string().email(),
});

class UserDto extends createStandardDto(UserSchema) {}

OpenAPI schema generation:

  • Zod v4+: Automatically generates OpenAPI schema via native toJSONSchema()
  • Zod v3.x / Other validators: Provide manual metadata
// For validators without native toJSONSchema (Zod v3.x, Valibot, etc.)
class UserDto extends createStandardDto(UserSchema, {
  openapi: {
    name: { type: 'string', example: 'John' },
    email: { type: 'string', format: 'email' },
  },
}) {}

Supported Validators

Any validator implementing the standard-schema specification:

| Validator | Version | Status | |-----------|---------|--------| | Zod | ^3.24 / ^4.0 | Tested | | Valibot | ^1.0.0 | Tested | | ArkType | ^2.0.0 | Compatible* | | TypeBox | ^0.32.0 | Compatible* | | And more... | | See full list |

*Compatible: Implements standard-schema spec but not tested in this package. PRs welcome!

Requirements

  • Node.js >= 18
  • NestJS >= 10.0.0
  • TypeScript >= 5.0

Requirements for Global Pipe

When using StandardValidationPipe as a global pipe (without explicitly passing a schema), it relies on TypeScript's design:paramtypes metadata to detect the DTO class and its schema. This is the same mechanism used by NestJS's built-in ValidationPipe.

Required tsconfig.json settings:

{
  "compilerOptions": {
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true
  }
}

Build tool compatibility:

| Build Tool | Support | Configuration | |------------|---------|---------------| | tsc | Supported | Default with above tsconfig | | SWC | Supported | Requires decoratorMetadata: true in .swcrc | | esbuild | Not supported | Does not emit decorator metadata | | Vite / Vitest | Not supported | Uses esbuild internally |

SWC configuration (.swcrc):

{
  "jsc": {
    "transform": {
      "legacyDecorator": true,
      "decoratorMetadata": true
    }
  }
}

If your build tool doesn't support decorator metadata, use explicit schema passing instead:

// Instead of relying on global pipe detection:
@Body() dto: CreateUserDto

// Explicitly pass the schema:
@Body(new StandardValidationPipe(CreateUserSchema)) dto: CreateUserDto

Contributing

Contributions are welcome! Please see CONTRIBUTING.md for guidelines.

Interested in adding support for a new validator? Check out ADDING_VALIDATORS.md.

License

MIT