asteasolutions/zod-to-openapi

`extendZodWithOpenApi` Not Working as Expected in NestJS

Closed this issue · 6 comments

Description:

I am trying to integrate the library into my NestJS application. According to the docs, the `extendZodWithOpenApi function should be called once at the entry point of the application. However, when I followed the instructions, I get the following error:

TypeError: z.string(...).email(...).openapi is not a function

Steps to Reproduce

  • Installed @asteasolutions/zod-to-openapi using pnpm.
  • Attempted to call extendZodWithOpenApi inside the bootstrap function in main.ts.
  • Additionally created an OnModuleInit service to call extendZodWithOpenApi the NestJS way.

Code Examples

main.ts

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { extendZodWithOpenApi } from '@asteasolutions/zod-to-openapi';
import { z } from 'zod';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  extendZodWithOpenApi(z); // Initial attempt to extend Zod
  await app.listen(3000);
}
bootstrap();

ZodToOpenApiService.ts

import { Injectable, OnModuleInit } from '@nestjs/common';
import { z } from 'zod';
import { extendZodWithOpenApi } from '@asteasolutions/zod-to-openapi';

@Injectable()
export class ZodToOpenApiService implements OnModuleInit {
  onModuleInit() {
    console.log('ZodToOpenApiService initialized');
    extendZodWithOpenApi(z);
  }
}

ZodToOpenApiModule.ts

import { Global, Module } from '@nestjs/common';
import { ZodToOpenApiService } from './zod-to-openapi.service';

@Global()
@Module({
  providers: [ZodToOpenApiService],
  exports: [ZodToOpenApiService],
})
export class ZodToOpenApiModule {}

Observations

  • Calling extendZodWithOpenApi inside the bootstrap function did not work.
  • Creating an OnModuleInit service to call extendZodWithOpenApi also did not work.
  • Directly calling extendZodWithOpenApi in the DTO file works, but the documentation suggests this should only be done once at the entry point.
  • Despite the compilation throwing a TypeError, the .openapi method becomes available in autocompletion by TypeScript when extendZodWithOpenApi is called from anywhere.

Expected Behavior

The extendZodWithOpenApi function should be called once and make the .openapi() method available on Zod schemas throughout the application.

Actual Behavior

The .openapi() method is not available on Zod schemas when extendZodWithOpenApi is called in main.ts or within an OnModuleInit service.

Environment

  • NestJS Version: 10.3.8
  • Node Version: v22.0.0
  • @asteasolutions/zod-to-openapi Version: 7.0.0
  • Zod Version: 3.23.8

Hey @Armadillidiid there are two things that I could try and suggest:

  1. Try and call extendZodWithOpenApi before actually creating the app in the bootstrap function.
  2. If that doesn't work then a patter that we've used and so have many other users of the library it to create a file (for example lib/zod.ts) where you do:
import { z } from 'zod';

extendZodWithOpenApi(z);

export { z }

and then you can use that instance of zod anywhere across your app to make sure it has been properly extended

I had already tried the first option and it didn't work. However, having one instance of Zod as you've said solved the problem. Thank you.

I'm facing a similar issue.
I used Cloudflare Chanfana embedded in an Astro project. Chanfana itself already calls extendZodWithOpenApi(z) (here) but it wasn't working for me.
So I called it mysql in some entry index.ts and that worked.

But suddenly it wasn't working anymore.. and I have no idea why. I moved the extendZodWithOpenApi(z) to another file and it worked again (for now?)...

Using the above solution to create that zod instance in a file and use that everywhere, doesn't cause that impact if you have a dependency in your project that imports zod as well...?

I was also using a wrapper like:

import { z } from 'zod';
import { extendZodWithOpenApi } from '@asteasolutions/zod-to-openapi';

extendZodWithOpenApi(z);

export { z };

and it worked until today... after adding some code using zo it now consistently fails with:
Cannot read properties of undefined (reading 'openapi') .. so I'm completely blocked now unfortunately.

In the code I only import z from my wrapper.

I will make a minimal reproducible project later to share here.

@marceloverdijk the error you mentioned sounds like using z.<someSchema>().openapi() failed - which means the generated schema was undefined which is quite odd. And doesn't really sound like something in the realm of our library. However you can create a reproducible project and we can take a look try and give an idea if there is anything related to our usage.

Hi @AGalabov thx for your feedback, I appreciate it.

I've create a minimal reproducible project here: https://github.com/marceloverdijk/astro-chanfana-zod-openapi

The project has 1 main scheme:

import { z } from '@/api/utils/zod';
import { BookTypeSchema, IsbnSchema } from '@/api/schemas';

// prettier-ignore
export const BookSchema = z
  .object({
    id: z.number().int().openapi({ description: 'The unique identifier of the book.', example: 1 }),
    isbn: IsbnSchema.openapi({ description: 'The unique ISBN of the book.', example: '978-0-7475-3269-9' }),
    title: z.string().openapi({ description: 'The title of the book.', example: 'Harry Potter and the Philosopher\'s Stone' }),
    type: BookTypeSchema.openapi({ description: 'The type of the book.', example: 'FANTASY' }),
    author: z.string().openapi({ description: 'The author of the book.', example: 'J. K. Rowling' }),
  })
  .openapi('Book', { description: 'Representation of a book.' });

I already noticed it goes wrong with re-using the IsbnSchema and BookTypeSchema schemas, but I cannot explain why.