nestjs-zod

repository·main·Indexed 22 days ago

https://github.com/benlorantfy/nestjs-zod

A validation solution for NestJS applications that integrates Zod for request validation, response serialization, and automatic OpenAPI documentation generation. It provides tools like ZodValidationPipe, ZodSerializerInterceptor, and createZodDto to replace class-validator and class-transformer. The package includes nestjs-zod-cli for automated project setup and integration.

Tokens
14K
Snippets
56
Records
66
Agent score
77%

What's inside nestjs-zod

  1. Reuse schemas in OpenAPI with `.meta({ id: ... })`

    main

    You can externalize and reuse schemas in your OpenAPI documentation by adding .meta({ id: "SchemaName" }) to a Zod schema. This adds the schema directly to components.schemas in the OpenAPI output.

    Naming Logic

    1. Input Schemas: Use the id provided in .meta({ id: 'MySchema' }).
    2. Output Schemas: nestjs-zod automatically suffixes the id with _Output (e.g., MySchema_Output) to prevent collisions with input schemas.
    3. Titles: If .meta({ title: 'CustomTitle' }) is used, that title is used instead of the ID.
    const Author = z.object({ name: z.string() }).meta({ id: "Author" })
    
    class BookDto extends createZodDto(z.object({ title: z.string(), author: Author })) { }
    // Result: 'Author' will be a reusable component in OpenAPI
  2. Handle `ZodValidationException` and `ZodSchemaDeclarationException`

    main

    ZodValidationException

    Thrown when request parsing fails. It returns a 400 status code with an errors array containing Zod error details. You can handle this using a NestJS ExceptionFilter and calling exception.getZodError() to access the raw ZodError.

    ZodSchemaDeclarationException

    Thrown when strictSchemaDeclaration is enabled and a parameter lacks a nestjs-zod DTO. This results in a 500 status code. You can handle this with an ExceptionFilter to provide a custom response.

    @Catch(ZodValidationException)
    export class ZodValidationExceptionFilter implements ExceptionFilter {
      catch(exception: ZodValidationException) {
        exception.getZodError() // -> ZodError
      }
    }
  3. Use Input vs Output schemas for OpenAPI generation

    main

    With the new OpenAPI generation in version 5.x, you can distinguish between 'input' and 'output' schemas:

    1. Input Schema: Represents the data shape required to pass validation. This is the default for DTOs created via createZodDto.
    2. Output Schema: Represents the return value of parsing.

    To use the output version in Swagger, use the .Output property on your DTO (e.g., @ApiResponse({ type: Car.Output })) or use the @ZodResponse decorator, which uses the output version automatically.

  4. Handle unknown error types from getZodError in version 5.x

    main

    In version 5.x, getZodError() on ZodValidationException or ZodSerializationException now returns unknown instead of ZodError. This change supports 'bringing your own zod' (v3, v4, or zod mini).

    You must use instanceof checks to narrow the type of the error to the specific Zod version you are using.

    import { ZodError as ZodErrorV3 } from 'zod/v3';
    import { ZodError as ZodErrorV4 } from 'zod/v4';
    
    @Catch(HttpException)
    export class HttpExceptionFilter extends BaseExceptionFilter {
        private readonly logger = new Logger(HttpExceptionFilter.name);
    
        catch(exception: HttpException, host: ArgumentsHost) {
            if (exception instanceof ZodSerializationException) {
                const zodError = exception.getZodError();
                if (zodError instanceof ZodErrorV3) {
                    this.logger.error(`ZodSerializationException: ${zodError.message}`);
                } else if (zodError instanceof ZodErrorV4) {
                    this.logger.error(`ZodSerializationException: ${zodError.message}`);
                }
            }
    
            super.catch(exception, host);
        }
    }
  5. Update zodToOpenAPI to zodV3ToOpenAPI in version 5.x

    main

    The function zodToOpenAPI is deprecated in version 5.x and has been renamed to zodV3ToOpenAPI.

    Note: If you are using Zod v4, you should consider using the built-in z.toJSONSchema(schema) method instead of this library's utility, as zodToOpenAPI is slated for removal.

    - const openApi = zodToOpenAPI(MySchema);
    + const openApi = zodV3ToOpenAPI(MySchema);
  6. Set up nestjs-zod in a NestJS application

    main

    To integrate nestjs-zod into your NestJS application, you need to configure three main components:

    1. Validation and Serialization: Register ZodValidationPipe and ZodSerializerInterceptor in your AppModule (or globally) to enable automatic request validation and response serialization using Zod schemas.
    2. OpenAPI Integration: Use cleanupOpenApiDoc in your main.ts to ensure that the generated OpenAPI/Swagger documentation is cleaned up and correctly reflects your Zod-based schemas.
    3. Error Handling: Implement a custom HttpExceptionFilter to catch Zod validation errors and format them into consistent, user-friendly HTTP error responses.
  7. Setup and run the NestJS ESM example project

    main

    This package is a TypeScript starter repository using the NestJS framework with ESM support.

    Project Setup

    Install dependencies using pnpm:

    pnpm install

    Running the Application

    Use the following commands to manage the application lifecycle:

    • Development: Run the project normally.
    • Watch Mode: Run the project with hot-reloading enabled.
    • Production: Run the compiled production build.

    Running Tests

    Execute the test suite using these commands:

    • Unit Tests: Run the unit test suite.
    • E2E Tests: Run end-to-end tests.
    • Test Coverage: Run tests and generate a coverage report.
    # development
    $ pnpm run start
    
    # watch mode
    $ pnpm run start:dev
    
    # production mode
    $ pnpm run start:prod
    
    # unit tests
    $ pnpm run test
    
    # e2e tests
    $ pnpm run test:e2e
    
    # test coverage
    $ pnpm run test:cov
  8. Automatic Setup with nestjs-zod-cli

    main

    You can automatically configure nestjs-zod in your NestJS project using the CLI. This runs a codemod that automatically adds the ZodValidationPipe, ZodSerializerInterceptor, HttpExceptionFilter, and the cleanupOpenApiDoc function to your project.

    Run the following command, replacing /path/to/nestjs/project with the actual path to your project root:

    npx nestjs-zod-cli /path/to/nestjs/project
  9. Configure ZodValidationPipe for Request Validation

    main

    To enable validation for request bodies, query parameters, and URL parameters, you must add ZodValidationPipe to your AppModule providers using the APP_PIPE token.

    import { APP_PIPE } from '@nestjs/core';
    import { ZodValidationPipe } from 'nestjs-zod';
    
    @Module({
      imports: [],
      controllers: [AppController],
      providers: [
        {
          provide: APP_PIPE,
          useClass: ZodValidationPipe,
        },
      ],
    })
    export class AppModule {}
  10. Migrate OpenAPI setup from version 4.x to 5.x

    main

    In version 5.x, patchNestJsSwagger() has been replaced by cleanupOpenApiDoc(). Instead of monkey-patching NestJS, you must now call cleanupOpenApiDoc() with your generated OpenAPI document before passing it to SwaggerModule.setup().

    - patchNestJsSwagger()
    - SwaggerModule.setup('api/zod-v3', app, openApiDoc);
    + SwaggerModule.setup('api/zod-v3', app, cleanupOpenApiDoc(openApiDoc));
  11. Setup and run the example-dual-zods project

    main

    This project is a NestJS framework TypeScript starter repository. Use pnpm to manage dependencies and run the application in various modes.

    Installation

    Install dependencies using:

    pnpm install

    Running the application

    • Development mode: pnpm run start
    • Watch mode (auto-reload on changes): pnpm run start:dev
    • Production mode: pnpm run start:prod
    $ pnpm install
    
    # development
    $ pnpm run start
    
    # watch mode
    $ pnpm run start:dev
    
    # production mode
    $ pnpm run start:prod