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 🙏

© 2024 – Pkg Stats / Ryan Hefner

@wahyubucil/nestjs-zod-openapi

v0.1.2

Published

NestJS helper to easily use Zod with OpenAPI

Downloads

165

Readme

NestJS Zod OpenAPI

NestJS helper to easily use Zod with OpenAPI

npm-image license-image typescript-image

This package is a combination of two awesome packages:

  • @anatine/zod-nestjs: provide a validation pipe on data and helper methods to create DTO from a Zod schema.
  • @asteasolutions/zod-to-openapi: provide openapi method to the Zod schema. The advantage of this package is the built-in schema reference, so when we have a nested schema, the OpenAPI documentation will reference the schema instead of just creating a plain new object.

Most of the documentation here will be adopted from both of the packages. If something's missing here, please refer to each package's documentation. Thanks to both of the authors for making such awesome packages.

Table of contents

Installation

Make sure you've set up the @nestjs/swagger module first. Read it here.

After set up is completed you can install this package and the Zod package:

# NPM
npm i zod @wahyubucil/nestjs-zod-openapi
# Yarn
yarn add zod @wahyubucil/nestjs-zod-openapi
# PNPM
pnpm add zod @wahyubucil/nestjs-zod-openapi

Zod requires us to enable strict mode in your tsconfig.json.

// tsconfig.json
{
  // ...
  "compilerOptions": {
    // ...
    "strict": true
  }
}

Example tsconfig.json:

{
  "compilerOptions": {
    "module": "commonjs",
    "declaration": true,
    "removeComments": true,
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "allowSyntheticDefaultImports": true,
    "target": "ES2021",
    "sourceMap": true,
    "outDir": "./dist",
    "baseUrl": "./",
    "incremental": true,
    "skipLibCheck": true,
    "strict": true
  }
}

Usage

Set up your app

  1. Make sure you load the @wahyubucil/nestjs-zod-openapi/boot script from this package at the top of the main script.
  2. Patch the swagger so that it can use Zod types before you create the document.

Example Main App:

import '@wahyubucil/nestjs-zod-openapi/boot' // <-- add this. The boot script should be on the top of this file.

import { NestFactory } from '@nestjs/core'
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger'
import { patchNestjsSwagger } from '@wahyubucil/nestjs-zod-openapi' // <-- add this. Import the patch for NestJS Swagger

import { AppModule } from './app.module'

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

  const config = new DocumentBuilder()
    .setTitle('Example API')
    .setDescription('The example API description')
    .setVersion('1.0')
    .build()

  patchNestjsSwagger({ schemasSort: 'alpha' }) // <-- add this. This function should run before the `SwaggerModule.createDocument` function.

  const document = SwaggerModule.createDocument(app, config)
  SwaggerModule.setup('docs', app, document)

  await app.listen(3000)
}
bootstrap()

The patchNestjsSwagger contains an options:

  • schemasSort: to determine the sorting mechanism
    • default: no special sorting mechanism, it's based on the schema declaration order.
    • alpha: sorting based on the alpha-numeric order.
    • localeCompare: using JavaScript localeCompare to sort the order, it will use each locale sorting mechanism.

Using ZodValidationPipe

There are two ways to do it:

  • Globally (recommended):
// app.module.ts
import { APP_PIPE } from '@nestjs/core'
import { ZodValidationPipe } from '@wahyubucil/nestjs-zod-openapi'

@Module({
  providers: [
    {
      provide: APP_PIPE,
      useClass: ZodValidationPipe,
    },
  ],
})
export class AppModule {}
  • Locally
// cats.controller.ts
import { ZodValidationPipe } from '@wahyubucil/nestjs-zod-openapi'

// controller-level
@UsePipes(ZodValidationPipe)
class CatsController {}

class CatsController {
  // route-level
  @UsePipes(ZodValidationPipe)
  async create() {}
}

Generate a schema

Use Zod to generate a schema.

Example schema:

// cats.dto.ts
import { createZodDto } from '@wahyubucil/nestjs-zod-openapi'
import { z } from 'zod'

// This will make a new Zod schema with the name 'User'
export const User = z
  .object({
    email: z.string(),
    displayName: z
      .string()
      .openapi({ description: 'Display name of the user' }), // <-- using the `openapi` method to add additional info on the field
  })
  .openapi('User')

// This will make a new Zod schema with the name 'CatShelter'
export const Shelter = z
  .object({
    name: z.string(),
    address: z.string(),
  })
  .openapi('CatShelter', { description: 'Shelter information' }) // <-- using the `openapi` method to add additional info on the schema

// Example DTO Schemas to be used on controller
export const Cat = z.object({
  name: z.string(),
  age: z.number(),
  breed: z.string(),
  shelter: Shelter,
  createdBy: User,
})
export class CatDto extends createZodDto(Cat) {}

export class UpdateCatDto extends createZodDto(
  Cat.omit({ name: true, createdBy: true }),
) {}

export const GetCats = z.object({
  cats: z.array(z.string()),
})
export class GetCatsDto extends createZodDto(GetCats) {}

export const CreateCatResponse = z.object({
  success: z.boolean(),
  message: z.string(),
  name: z.string(),
})
export class CreateCatResponseDto extends createZodDto(CreateCatResponse) {}

export class UpdateCatResponseDto extends createZodDto(
  CreateCatResponse.omit({ name: true }),
) {}

Use the schema in controller

This follows the standard NestJS method of creating controllers.

@nestjs/swagger decorators should work normally.

Example Controller:

// cats.controller.ts
import { Body, Controller, Get, Param, Patch, Post } from '@nestjs/common'
import { ApiCreatedResponse, ApiOkResponse } from '@nestjs/swagger'
import {
  CatDto,
  CreateCatResponseDto,
  GetCatsDto,
  UpdateCatDto,
  UpdateCatResponseDto,
} from './cats.dto'

@Controller('cats')
export class CatsController {
  // Use DTO as the type-safety helper
  @Get()
  @ApiOkResponse({
    type: GetCatsDto,
  })
  async findAll(): Promise<GetCatsDto> {
    return { cats: ['Lizzie', 'Spike'] }
  }

  // If you want both runtime and type safety, we recommend to parse the schema directly instead of using it as a return type
  @Get(':id')
  @ApiOkResponse({
    type: CatDto,
  })
  async findOne(@Param() { id }: { id: string }) {
    return CatDto.zodSchema.parse({
      name: `Cat-${id}`,
      age: 8,
      breed: 'Unknown',
      shelter: {
        name: 'A sample shelter',
        address: 'Bali, Indonesia',
      },
      createdBy: {
        email: '[email protected]',
        displayName: 'Wahyu Bucil',
      },
    })
  }

  @Post()
  @ApiCreatedResponse({
    description: 'The record has been successfully created.',
    type: CreateCatResponseDto,
  })
  async create(@Body() createCatDto: CatDto) {
    return CreateCatResponseDto.zodSchema.parse({
      success: true,
      message: 'Cat created',
      name: createCatDto.name,
    })
  }

  @Patch()
  @ApiOkResponse({
    type: UpdateCatDto,
  })
  async update(@Body() updateCatDto: UpdateCatDto) {
    return UpdateCatResponseDto.zodSchema.parse({
      success: true,
      message: `Cat's age of ${updateCatDto.age} updated`,
    })
  }
}

NOTE: You still need to explicitly define the response for the OpenAPI via the decorators provided by @nestjs/swagger.