risen228 / nestjs-zod Goto Github PK
View Code? Open in Web Editor NEWAll NestJS + Zod utilities you need
License: MIT License
All NestJS + Zod utilities you need
License: MIT License
We have a guard with the following code:
const result = z
.strictObject({ id: z.number() })
.safeParse(request.params)
if (!result.success) {
throw new ZodValidationException(result.error)
}
const id = result.data
This logic is basically what the validate
function does. We would gladly use this function instead, but it seems to not be exported.
Is it possible to export the validate
function?
Hello,
I'm trying to reproduce the first steps but i'm getting this error. Could someone help me overpass it?
Property 'string' does not exist on type 'typeof import("service/node_modules/nestjs-zod/dist/z-only-override")'.
You're trying to access object on an object that doesn't contain it.
<< package.json >>
"dependencies": {
"@nestjs/common": "^9.3.12",
"@nestjs/core": "^9.3.12",
"@nestjs/swagger": "^6.2.1",
"nestjs-zod": "^2.2.0",
},
"devDependencies": {
"typescript": "^4.3.5"
},
node v18.15.0
Hi.
First off, I'm happy there is a project using Zod instead of having to deal with class-validator. However, the createZodDto
function does not create a class instance, but rather an object.
const createEnvironmentSchema = z.object({
label: z.string().max(191).nonempty(),
fromName: z.string().max(191).nonempty(),
fromEmail: z.string().max(191).email(),
smtpHost: z.string().max(191).nonempty(),
smtpPort: z.number().int().max(65535),
smtpUser: z.string().max(191).nonempty(),
smtpPass: z.string().max(191).nonempty(),
})
export class CreateEnvironmentDto extends createZodDto(createEnvironmentSchema) {
mailbox(): string {
return `"${this.fromName}" <${this.fromEmail}>`
}
}
I expected the function to return an instance of the class, so I can add additional methods and acessors, and be able to use them as well as instanceof
calls downstream, as I've been accustomed to do using class-transfromer
. However, the value returned to the controller is a POJO.
@Controller('environments')
export class EnvironmentsController {
@Post()
async create(@Body() dto: CreateEnvironmentDto) {
console.log(dto, dto instanceof CreateEnvironmentDto)
}
}
The above console statement produces { ... }, false
instead of CreateEnvironmentDto { ... }, true
Another issue is that the object properties are all optional whereas the Zod schema has no optional properties. The LSP reports the shape of the object as such:
label?: string;
fromName?: string;
fromEmail?: string;
smtpHost?: string;
smtpPort?: number;
smtpUser?: string;
smtpPass?: string;
The actual type shown is this:
(alias) createZodDto<{
label?: string;
fromName?: string;
fromEmail?: string;
smtpHost?: string;
smtpPort?: number;
smtpUser?: string;
smtpPass?: string;
}, z.ZodObjectDef<{
label: z.ZodString;
fromName: z.ZodString;
fromEmail: z.ZodString;
smtpHost: z.ZodString;
smtpPort: z.ZodNumber;
smtpUser: z.ZodString;
smtpPass: z.ZodString;
}, "strip", z.ZodTypeAny>, {
...;
}>(schema: z.ZodType<...>): ZodDto<...>
import createZodDto
This keeps it from being useful for downstream API's where it should be assumed that all required properties are present in the DTO, which of course they are, but the LSP doesn't know that.
Using the most recent versions of this library and NestJS:
"@nestjs/common": "^10.0.0",
"@nestjs/core": "^10.0.0",
"nestjs-zod": "^2.3.3",
"zod": "^3.22.2",
Hopefully there is a fix? I REALLY do not want to go back to using class-validator
and vlass-transformer
which are just tedious to deal with.
Hey @risen228, great job on creating this package! NestJS creator here.
I recently started working on the @nestjs/zod
package to simplify the integration between the two (Nest and Zod), and then I came across your package and noticed that it already provides several features I planned to add to the official library (great job!)
So I'd like to suggest a few improvements/questions, just in case you have some time:
@nestjs/swagger
, you could add a static _OPENAPI_METADATA_FACTORY
method to your Zod DTO classes (see screenshot below - from my local project)Fantastic job again!
When the schema contains a union I get an error:
error TS2509: Base constructor return type [...] is not an object type or intersection of object types with statically known members
example:
import { z } from 'nestjs-zod/z';
const unionTypeSchema = z.union([z.string(), z.number()]);
class Dto extends createZodDto(unionTypeSchema) {}
version: 3.0.0
I'm trying to create a dto from a zod discriminated union like this:
const BaseSchema = z.object({
names: z.string(),
last_name: z.string(),
document_id: z.string(),
document_type: z.nativeEnum(DocumentType),
nationality: z.string(),
email: z.string().email(),
});
const EmployeeSchema = BaseSchema.extend({
user_type: z.literal(UserType.EMPLOYEE), // UserType.EMPLOYEE === "EMPLOYEE"
});
const SellerSchema = BaseSchema.extend({
user_type: z.literal(UserType.SELLER), // UserType.SELLER === "SELLER"
code: z.number(),
});
const CreateUserBodySchema = z.discriminatedUnion('user_type', [
EmployeeSchema,
SellerSchema,
]);
export class CreateUserBodyDto extends createZodDto(CreateUserBodySchema) {}
createZodDto(CreateUserBodySchema)
yields the following error: TS2509
Base constructor return type '{ names: string; last_name: string; document_id: string; document_type: "PASSPORT" | "RUT"; nationality: string; email: string; user_type: "HHRR"; } | { names: string; last_name: string; document_id: string; ... 4 more ...; user_type: "SELLER"; }' is not an object type or intersection of object types with statically known members.
nestjs-zod version: 2.2.0
typescript version: 4.9.5
Please Support setErrorMap from zod library
I've registered ZodSerializerInterceptor as an APP_INTERCEPTOR in my root module.
It looks like that when a response object does not match what is defined in @ZodSerializerDto() a 400 error is returned, along with its error details.
I think that errors in response validation should return 500 instead, if the server doesn't return what it's supposed to that's a server error, isn't it?
HI, I tried to use the library and I get a vulnerability error in the package that it has as a dependency merge-deep 3.0.3
With class-validator, we have ClassSerializerInterceptor to strip out unwanted properties from responses. This can be useful if you would like to exclude additional properties from being returned to the client that should not be exposed.
Have a ZodSerializerInterceptor
similar to ClassSerializerInterceptor
to transform response before client receives it
User Shape
type User {
name: string
password: string
}
Response Schema
export const responseSchema = z.object({'name': z.string() })
Controller Class
@ZodSerializerInterceptor(responseSchema)
getUser(@Param() id: number) {
return this.userService.findOne(id) // we return the user object with password property here
}
In the above example, even though userService.findOne(id)
returns the password
property, with our ZodSerializerInterceptor
, we ensure that the password
property gets stripped.
If this gets approved, i'll be happy to do up a PR to implement this feature.
It looks like the @ZodSerializerDto
attribute isn't being used when generating the OpenAPI
spec.
I have a method on a controller that looks like
@Get('foo')
@ZodSerializerDto(TestSchemaDto)
test() {
return { id: '3' }
}
However, when I generate the OpenAPI
document, I get
"/api_keys/foo": {
"get": {
"operationId": "ApiKeyController_test",
"parameters": [],
"responses": {
"200": {
"description": ""
}
}
}
},
Explicitly adding the return type to the method does write out the info to the swagger file
"/api_keys/foo": {
"get": {
"operationId": "ApiKeyController_test",
"parameters": [],
"responses": {
"200": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TestSchemaDto"
}
}
}
}
}
}
},
And just for completeness, here is my zod dto
export const TestSchema = z.object({
id: z.string(),
})
export class TestSchemaDto extends createZodDto(TestSchema) {}
Hello,
Is there any way to add the example property to the generated OpenAPI?
I'm interested to use this for DTOs. This is usually possible when declaring DTOs manually with the ApiProperty
decorator, but I'm not sure how to do it when using createZodDto
.
Thanks!
I am wondering if its possible to add binary type to a field for example
LogoFIle: z.binary()
OR
LogoFIle: z.string().binary()
Just so when i use formData i am able to see that file on swagger !
Hi, I've an usecase where I need to pass custom options to Zod's .parse()
or .safeParse()
but I couldnt find anywhere in the docs on how to do that.
Basically, I need to pass an errorMap
for localization, that depends on a query from the request.
Is there anyway to actually create a validate
function without using the bultin? Where we've access to the request and have to call Zod ourselves?
Dto by following docs
import { createZodDto } from 'nestjs-zod';
import { z } from 'nestjs-zod/z';
const CreateFeedSchema = z.object({
title: z.string(),
body: z.string(),
// attachment: z.
});
export class CreateFeedDto extends createZodDto(CreateFeedSchema) {}
in the controller
@Post()
create(@Body() createFeedDto: CreateFeedDto) {
const d = createFeedDto. // << Intellisense gone! no suggestion
return this.feedsService.create(createFeedDto);
}
I've tried to create dto for path param using createZodDto
but it gives no openapi description for those class.
In example below I've showed one example without zod(it works fine) and extended by createZodDto
class.
Here is reproduction example:
import { Controller, Get, Param } from '@nestjs/common'
import { ApiProperty } from '@nestjs/swagger'
import { createZodDto } from 'nestjs-zod'
import { z } from 'nestjs-zod/z'
class SingleIdDto {
@ApiProperty({ type: () => Number })
id: number
}
export class SingleIdDto2 extends createZodDto(
z.object({
id: z.number().int().positive()
})
) {}
@Controller('samples')
export class SampleController {
@Get('/:id')
async getHello(@Param() params: SingleIdDto2): Promise<any> {
console.log({ params })
return {}
}
}
Getting TypeError: Cannot read properties of undefined (reading 'isLazyTypeFunc')
when I use patchNestJsSwagger
and I try to apply a ApiBadRequestResponse
decorator from @nestjs/swagger. Is there any way to extend the OpenAPI object through @nestjs/swagger decorators when using patchNestJsSwagger?
If a user has moduleResolution
set to node16
(or nodenext
, probably) they will get this error when importing nestjs-zod/z
:
src/example.ts:1:19 - error TS7016: Could not find a declaration file for module 'nestjs-zod/z'. '/Users/jonah/example/node_modules/nestjs-zod/dist/z.js' implicitly has an 'any' type.
If the 'nestjs-zod' package actually exposes this module, try adding a new declaration (.d.ts) file containing `declare module 'nestjs-zod/z';`
1 import { z } from 'nestjs-zod/z';
I believe this is because the package.json
does not specify types
fields for any of the conditional exports. Because we don't explicitly say where to look for declarations, tsc
checks the adjacent file (ex. dist/z.js
-> dist/z.d.ts
), but this file doesn't exist, causing the error.
When using a .refine
that returns a Promise
I get the following error from Zod:
Async refinement encountered during synchronous parse operation. Use .parseAsync instead.
I assume nestjs-zod
uses .parse
under the hood. Is there a way to tell nestjs-zod
to use .parseAsync
instead?
{
"statusCode": 400,
"message": "Validation failed",
"errors": [
{
"code": "invalid_type",
"expected": "string",
"received": "undefined",
"path": [
"title"
],
"message": "Required"
},
...
]
}
{
"statusCode": 400,
"message": [
"property title should not exist",
"property currency should not exist",
"property logo should not exist",
"property road should not exist",
"property propertyNumber should not exist",
"property zip should not exist",
"property city should not exist",
"property country should not exist"
],
"error": "Bad Request"
}
import { createZodDto } from 'nestjs-zod';
export class CreateCompanyDto extends createZodDto(CreateCompanyInfoFormSchemaBE) {}
this is the controller.
Nest JS version: 9.2.1
nestjs-zod: 2.2.3
Say I have this schema:
export const BaseCreateAppointmentDtoSchema = z.object({
title: z.string(),
description: z.string().nullish(),
startAt: z.coerce.date(),
endAt: z.coerce.date(),
place: z.string().nullish(),
internalGuestsEmails: z.array(z.string().email()).optional(),
externalGuestsEmails: z.array(z.string().email()).optional(),
isOnlineMeeting: z.boolean().optional(),
})
export const CreateAppointmentDtoSchema = BaseCreateAppointmentDtoSchema.transform((dto) => {
console.log('transform rodou!')
dto.internalGuestsEmails = uniq(dto.internalGuestsEmails)
dto.externalGuestsEmails = uniq(dto.externalGuestsEmails)
return dto
})
export type CreateAppointmentDto = z.infer<typeof CreateAppointmentDtoSchema>
The object that gets to my controller does not have the email lists uniquefied. Would it be possible to make the transforms apply?
Thanks for the awesome library.
Hey I'm pretty stoked about the direction that this library is headed so I thought I'd pop it into our application tonight and see if would possibly give me a path to move away from Class-Transformer and Class-Validator on the backend.
We already have a fair number of endpoints and functionality in the app that heavily uses all the @nestjs/swagger functionality so that we can generate a type safe client to talk to the API from our frontend.
My versions are:
@nestjs/swagger 5.2.1
@nestjs/{common/core} 8.4.7
I'm invoking patchNestJsSwagger();
near the top of my main.ts
file which seems to be what the doc suggests.
When I boot up the app I get the following stack trace:
/home/jesse/code/work/circadian-risk/project-ember-web/node_modules/@nestjs/swagger/dist/services/schema-object-factory.js:89
if (this.isLazyTypeFunc(type)) {
^
TypeError: Cannot read properties of undefined (reading 'isLazyTypeFunc')
at exploreModelSchema (/home/jesse/code/work/circadian-risk/project-ember-web/node_modules/@nestjs/swagger/dist/services/schema-object-factory.js:89:18)
at SchemaObjectFactory.e.exploreModelSchema (/home/jesse/code/work/circadian-risk/project-ember-web/node_modules/nestjs-zod/dist/index.js:1:13822)
at /home/jesse/code/work/circadian-risk/project-ember-web/node_modules/@nestjs/swagger/dist/services/schema-object-factory.js:33:36
at Array.map (<anonymous>)
at SchemaObjectFactory.createFromModel (/home/jesse/code/work/circadian-risk/project-ember-web/node_modules/@nestjs/swagger/dist/services/schema-object-factory.js:20:45)
at exploreApiParametersMetadata (/home/jesse/code/work/circadian-risk/project-ember-web/node_modules/@nestjs/swagger/dist/explorers/api-parameters.explorer.js:33:55)
at /home/jesse/code/work/circadian-risk/project-ember-web/node_modules/@nestjs/swagger/dist/swagger-explorer.js:72:45
at Array.reduce (<anonymous>)
at /home/jesse/code/work/circadian-risk/project-ember-web/node_modules/@nestjs/swagger/dist/swagger-explorer.js:71:99
at /home/jesse/code/work/circadian-risk/project-ember-web/node_modules/lodash/lodash.js:13469:38
at /home/jesse/code/work/circadian-risk/project-ember-web/node_modules/lodash/lodash.js:4967:15
at baseForOwn (/home/jesse/code/work/circadian-risk/project-ember-web/node_modules/lodash/lodash.js:3032:24)
at Function.mapValues (/home/jesse/code/work/circadian-risk/project-ember-web/node_modules/lodash/lodash.js:13468:7)
at MapIterator.iteratee (/home/jesse/code/work/circadian-risk/project-ember-web/node_modules/@nestjs/swagger/dist/swagger-explorer.js:71:45)
at MapIterator.next (/home/jesse/code/work/circadian-risk/project-ember-web/node_modules/iterare/src/map.ts:9:39)
at FilterIterator.next (/home/jesse/code/work/circadian-risk/project-ember-web/node_modules/iterare/src/filter.ts:11:34)
Curious if you have any top of mind thoughts for whats going on here if not I can try and make a minimal repro and see if the same issue can be shown outside of my work project
When I use createZodDto
with a schema like:
const CreateFanDto = z
.object({
gender: z.enum(['male', 'female', 'other']).nullish(),
status: z.enum(['active', 'inactive']).optional(),
firstname: z.string().nullish(),
lastname: z.string().nullish(),
phone: z.string().nullish(),
address: z.string().nullish(),
birthDate: z.string().datetime().nullish(),
email: z.string().email(),
})
It generates birthDate
as string
without format, but like indicated in the README for v3.0.0 it should accept native datetime and should generate the field as string with $datetime
format.
So I have a Zod schema createAddressSchema
that works out to be the type below.
ZodObject<{name: ZodOptional<ZodNullable<ZodString>>, firstLine: ZodString}>
When I turn this into a class
import { createZodDto } from 'nestjs-zod';
export class NewAddress extends createZodDto(createAddressSchema) {}
However, looking at the properties on the generated class, every property is optional, even though firstLine
should not be. Is there a reason for this?
{
name?: string;
firstLine?: string;
}
There is a chance it has to do with how I'm generating the Zod schema but since the type works out to not be wrapped in ZodOptional
it seems like it should be fine.
Thanks!
I am using ts-rest in a NestJS application and I've encountered an issue with interceptors. I have an interceptor that is supposed to modify the response before it is sent. However, when I try to access the response data in the interceptor, I get a Promise instead of the resolved data.
Here is a simplified version of my interceptor:
@Injectable()
export class MyInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
return next.handle().pipe(
tap((data) => {
console.log("data", data);
// Modify the response...
}),
);
}
}
I use the interceptor in a controller method like this (using the single handler approach):
@TsRestHandler(c.login)
@UseInterceptors(AuthDataInterceptor)
@UseGuards(EmailPasswordAuthGuard)
async login(@AuthDataFromGuard() authData: AuthData) {
return tsRestHandler(c.login, async () => {
// ...
return {
status: 201,
body: authData,
}
});
}
When I log the tapped data in the interceptor, I get a Promise, not the resolved data. This makes it impossible for me to modify the response as intended. I would expect the interceptor to receive the resolved data, not a Promise.
Is this a known issue, or am I doing something wrong? Any help would be appreciated.
Hey @risenforces!
My schema includes the usage of z.union
. Creating the DTO using createZodDto
works fine, but creating a class that extends
the result of the call to createZodDto
doesn't work.
import { createZodDto } from "nestjs-zod"
import { z } from "nestjs-zod/z"
const schema = z.union([z.number(), z.string()])
const dto = createZodDto(schema)
class Dto extends dto {}
The code above produces the following error:
src/test.dto.ts:8:19 - error TS2509: Base constructor return type 'string | number' is not an object type or intersection of object types with statically known members.
27 class Dto extends dto {}
~~~
After playing a bit with the library's code, I found that changing the signature of new
in ZodDto
to return this
suddenly works. The following code contains my own implementations of ZodDto
and createZodDto
.
import { ZodSchema, ZodTypeDef, z } from "nestjs-zod/z"
export interface ZodDto<
TOutput = any,
TDef extends ZodTypeDef = ZodTypeDef,
TInput = TOutput
> {
new (): this
isZodDto: true
schema: ZodSchema<TOutput, TDef, TInput>
create(input: unknown): TOutput
}
export function createZodDto<
TOutput = any,
TDef extends ZodTypeDef = ZodTypeDef,
TInput = TOutput
>(schema: ZodSchema<TOutput, TDef, TInput>) {
class AugmentedZodDto {
public static isZodDto = true
public static schema = schema
public static create(input: unknown) {
return this.schema.parse(input)
}
}
return AugmentedZodDto as unknown as ZodDto<TOutput, TDef, TInput>
}
const schema = z.union([z.number(), z.string()])
const dto = createZodDto(schema)
class Dto extends dto {}
The change is basically from this:
- new (): TOutput;
+ new (): this
Unless I'm missing something, it doesn't make sense to me not to return this
from the constructor.
This also makes me consider whether we even need the ZodDto
interface. Couldn't we replace it with the (currently dynamic) AugmentedZodDto
class?
Hi,
When working with class-validator
in nestjs, a very common problem I saw in several projects was that the DTOs weren't actually turned into instances of the specified dto class. For example
@Post()
async create(@Body() dto: CreateMarketDto) {}
Without setting {transform: true}
in the ValidationPipeOptions
(documented here), dto was just a plain javascript object at runtime. At compile time, however, this looks like dto instanceof CreateMarketDto
should return true, meaning also that any methods on the DTO will not give me compile errors.
nestjs-zod
seems to suffer from the same problem. What am I missing or what's the best way to avoid this issue (besides the obvious 'rule' in the team to not put methods on Dto classes)? Is there a way to make dto
into actual instances of CreateMarketDto
?
Here's a minimal example that shows that TS does not show compiler errors but runtime errors.
export class CreateMarketDto extends createZodDto(
z.object({ name: z.string() }),
) {
hello() {
console.log('hello');
}
}
@Controller('markets')
export class MarketsController {
@Post()
async create(@Body() dto: CreateMarketDto) {
dto.hello();
return market;
}
}
// in app.module
providers: [
{
provide: APP_PIPE,
useClass: ZodValidationPipe,
},
],
Calling the controller causes TypeError: dto.hello is not a function
Hello!
I have tried to use the lib using the example below
https://github.com/risenforces/nestjs-zod#creating-dto-from-zod-schema
But I got a this error:
error TS2551: Property 'schema' does not exist on type 'typeof import("/node_modules/.pnpm/[email protected]_cw6o3dkuyuli4a5gq7b223evly/node_modules/nestjs-zod/dist/z-only-override")'. Did you mean 'Schema'?
Example in Stackblitz: https://stackblitz.com/edit/node-jxr258?file=main.ts
I have looked for it in to the file and can't found the method schema
or Schema
.
I change the method z.schema
to z.object
and works fine. Is the example wrong?
NestJS version: 9.0.8
NestJS Zod version: 1.1.2
Typescript version: 4.7.4
Thanks!
Started to use YarnPnp on a monorepo project.
However when I attempt to run my NestJs app which uses nestjs-zod, I get the following error in my terminal:
Error: nestjs-zod tried to access rxjs, but it isn't declared in its dependencies; this makes the require call ambiguous and unsound.
Required package: rxjs
Required by: nestjs-zod@virtual:ace0b506873355d11cd586117594d7eb22f90ff3a2d2c145296f0ff6acc08f08194cf3dbc82554ba9fa4f646cc58853bb4c8c565dd25360ab7f67bae8a39b6db#npm:3.0.0 (via <oproject-root>/.yarn/__virtual__/nestjs-zod-virtual-f8417711e6/3/.yarn/berry/cache/nestjs-zod-npm-3.0.0-162e2c9a86-10c0.zip/node_modules/nestjs-zod/dist/)
Require stack:
- <oproject-root>/.yarn/__virtual__/nestjs-zod-virtual-f8417711e6/3/.yarn/berry/cache/nestjs-zod-npm-3.0.0-162e2c9a86-10c0.zip/node_modules/nestjs-zod/dist/index.js
- <oproject-root>/test-api/dist/dto/CreatePlotDTO.js
- <oproject-root>/test-api/dist/app.controller.js
- <oproject-root>/test-api/dist/app.module.js
- <oproject-root>/test-api/dist/main.js
at Function.require$$0.Module._resolveFilename (<oproject-root>/.pnp.cjs:15160:13)
at Function.Module._load (node:internal/modules/cjs/loader:920:27)
at Function.require$$0.Module._load (<oproject-root>/.pnp.cjs:15051:31)
at Module.require (node:internal/modules/cjs/loader:1141:19)
at require (node:internal/modules/cjs/helpers:110:18)
at Object.<anonymous> (<oproject-root>/.yarn/__virtual__/nestjs-zod-virtual-f8417711e6/3/.yarn/berry/cache/nestjs-zod-npm-3.0.0-162e2c9a86-10c0.zip/node_modules/nestjs-zod/dist/index.js:8:12)
at Module._compile (node:internal/modules/cjs/loader:1254:14)
at Object.Module._extensions..js (node:internal/modules/cjs/loader:1308:10)
at Object.require$$0.Module._extensions..js (<oproject-root>/.pnp.cjs:15203:33)
at Mo
https://github.com/risen228/nestjs-zod/blob/main/package.json#L61
I can see the "rxjs" dep is listed as a "devDependency"; is this something we can change to an actual dependency?
Hi
In the README you mention to use the @ ZodResponseDto decorator, but it's nowhere to be found.
Where do I get it from?
Branded types in zod are very helpful in ensuring that a function argument or a value has been validated through a zod schema.
Today when using .brand
in a schema, the zodToOpenAPI
function doesn't generate the correct type
at OpenAPI schema
import { createZodDto } from 'nestjs-zod'
import { z } from 'nestjs-zod/z'
const LoginSchema = z.object({
email: z.string().email().nonempty(),
password: z.password()
.atLeastOne('digit')
.atLeastOne('lowercase')
.atLeastOne('special')
.atLeastOne('uppercase')
.min(6),
})
export class LoginDto extends createZodDto(LoginSchema) {}
The only thing that gets validated is if I don't pass a password, or if the length is less than 6.
All the other validations like atLeastOne('special')
doesn't run.
@nestjs/core: 10.1.2
@nestjs-zod: 1.2.3
typescript: 5.1.6
I ask because our use of zod
is such that we have schemas defined in a shared package used among several applications. When combining them, we get type errors whenever the zod
types change between versions. As of this writing, the version of zod
used in this library is 3.14.3
while the latest is 3.20.2
, and there are a few type incompatibilities we have already encountered.
I am also wondering whether or not this project is actively maintained and are there plans to continue maintaining it?
Thanks!
Hi there,
First up, loving the library and amazed it doesn't have more stars.
I have applied ZodValidationPipe
globally and it's working a treat removing any unspecified properties from requests according to the request DTO. What I'd ideally like though is for an error to be thrown if a non whitelisted property is present in the request, rather than it just silently being stripped.
Is this possible?
Thanks :-)
I have a zod schema that has an object with a date field, and I'm using the "@nestjs/swagger"
to generate an OpenAPI json file. The schema type for the date field isn't generating as expected.
Other strings generate as expected.
This is what my zod schema and dto look like, as well as my controller using them:
import { Body, Controller, Post } from "@nestjs/common"
import { ApiBody } from "@nestjs/swagger"
import { createZodDto, zodToOpenAPI } from "nestjs-zod"
import { z } from "nestjs-zod/z"
const DateSchema = z.object({
date: z.dateString().format("date"),
})
class DateDto extends createZodDto(DateSchema) {}
const schema = zodToOpenAPI(DateSchema)
console.log({ schema })
@Controller("/test")
export class TestController {
@Post("/date")
@ApiBody({
schema,
})
async setDate(@Body() { date }: DateDto) {
console.log(date)
}
}
The generated OpenAPI json file does not have a type or format specified:
{
"schema": {
"type": "object",
"properties": {
"date": {}
},
"required": [
"date"
]
}
}
I expect that my generated OpenAPI json file should specify that this field is a string with a date format:
{
"schema": {
"type": "object",
"properties": {
"date": {
"type": "string",
"format": "date"
},
}
"required": [
"date"
]
}
}
node: v16.16.0
nestjs: ^9.0.0 (9.0.11)
nestjs-zod
: 1.2.1
Hello, I am using Prisma
Zod
and nestjs-zod
and nestjs-zod-prisma
Which should be compitable with each others
I am generating the Zod schemas with npx prisma generate
And everything goes well except for Role Enum
schema.prisma
enum Role {
USER
STORE_OWNER
}
so when i run the npx prisma generate
A Schema is generated based on my prisma table definitions
zod/storeowner.ts
import { Role } from './enums';
export const StoreOwnerModel = z.object({
role: z.nativeEnum(Role).default(Role.STORE_OWNER),
// etc...
});
export class StoreOwnerEntity extends createZodDto(StoreOwnerModel) {}
zod/enums.ts
export enum Role {
USER = "USER",
STORE_OWNER = "STORE_OWNER"
}
When using the StoreOwnerEntity in my service it is giving ts
error
store-owner.service.ts
async findFirst(): Promise<StoreOwnerEntity | null> {
return this.prismaService.storeOwner.findFirst();
}
Error
Types of property 'role' are incompatible.
Type 'import("c:/Users/saybers/Desktop/qbite/backend/node_modules/.prisma/client/index").$Enums.Role' is not assignable to type 'import("c:/Users/saybers/Desktop/qbite/backend/src/modules/zod/enums").Role'.
Type '"USER"' is not assignable to type 'Role'.ts(2322)
After looking through the issues I saw that this was already reported two times, which should be fixed with the MR #18, but seems still an Issue here.
My only dto:
import { PartialType } from '@nestjs/swagger';
import { createZodDto } from 'nestjs-zod';
import { z } from 'nestjs-zod/z';
const CreateLinkSchema = z.object({
url: z.string().url(),
slug: z.string().min(1).max(15),
});
export class CreateLinkDto extends createZodDto(CreateLinkSchema) {}
export class UpdateLinkDto extends PartialType(CreateLinkDto) {}
main.ts
import { Logger } from '@nestjs/common';
import { NestFactory } from '@nestjs/core';
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
import { patchNestJsSwagger } from 'nestjs-zod';
import { AppModule } from './app.module';
import env from './env';
async function bootstrap() {
const logger = new Logger('Main');
const app = await NestFactory.create(AppModule);
if (env.devMode) {
patchNestJsSwagger();
const documentBuilder = new DocumentBuilder()
.setTitle('URL Shortener backend')
.setVersion('1.0')
.build();
const document = SwaggerModule.createDocument(app, documentBuilder);
SwaggerModule.setup('docs', app, document);
}
logger.log(`App listening on port ${env.port}`);
await app.listen(env.port);
}
bootstrap();
Throws this exception:
path/backend/node_modules/.pnpm/@[email protected]_bgmtsjzu2jyijhogwtog4e3a6e/node_modules/@nestjs/swagger/dist/services/schema-object-factory.js:89
if (this.isLazyTypeFunc(type)) {
^
TypeError: Cannot read properties of undefined (reading 'isLazyTypeFunc')
at exploreModelSchema (path/backend/node_modules/.pnpm/@[email protected]_bgmtsjzu2jyijhogwtog4e3a6e/node_modules/@nestjs/swagger/dist/services/schema-object-factory.js:89:18)
at SchemaObjectFactory.e.exploreModelSchema (path/backend/node_modules/.pnpm/[email protected]_kwb2a4f7bk4iyyo5b2u32jtvxm/node_modules/nestjs-zod/dist/index.js:1:13958)
at path/backend/node_modules/.pnpm/@[email protected]_bgmtsjzu2jyijhogwtog4e3a6e/node_modules/@nestjs/swagger/dist/services/schema-object-factory.js:33:36
at Array.map (<anonymous>)
at SchemaObjectFactory.createFromModel (path/backend/node_modules/.pnpm/@[email protected]_bgmtsjzu2jyijhogwtog4e3a6e/node_modules/@nestjs/swagger/dist/services/schema-object-factory.js:20:45)
at exploreApiParametersMetadata (path/backend/node_modules/.pnpm/@[email protected]_bgmtsjzu2jyijhogwtog4e3a6e/node_modules/@nestjs/swagger/dist/explorers/api-parameters.explorer.js:35:55)
at path/backend/node_modules/.pnpm/@[email protected]_bgmtsjzu2jyijhogwtog4e3a6e/node_modules/@nestjs/swagger/dist/swagger-explorer.js:72:45
at Array.reduce (<anonymous>)
at path/backend/node_modules/.pnpm/@[email protected]_bgmtsjzu2jyijhogwtog4e3a6e/node_modules/@nestjs/swagger/dist/swagger-explorer.js:71:104
at path/backend/node_modules/.pnpm/[email protected]/node_modules/lodash/lodash.js:13469:38
package.json - reduced to minimum
"dependencies": {
"@nestjs/common": "^9.0.0",
"@nestjs/core": "^9.0.0",
"@nestjs/platform-express": "^9.0.0",
"nestjs-zod": "^1.2.3",
"zod": "^3.21.4"
},
"devDependencies": {
"@nestjs/cli": "^9.0.0",
"@nestjs/schematics": "^9.0.0",
"@nestjs/swagger": "^6.2.1",
"@nestjs/testing": "^9.0.0",
"tsconfig-paths": "4.1.0",
"typescript": "^4.7.4"
}
Greetings! I recently discovered Zod as a way to validate JSON schemas internally, and I absolutely love the project. I have a fairly extensive API, and would love to migrate the DTOs to Zod as well so that I can have a single source of truth for all of the data types, OpenAPI schema, and validation. With class validator, the reliance on decorators for validation and schema generation means that there is no guarantee that the type annotations match the decorators.
Is there a recommended migration pathway that doesn't require removing the useGlobalPipes for class transformer? If so, it would certainly be worth pointing out in the library documentation IMO.
Please support the extraction of inferred types from schemas.
Original Zod supports the extraction of TypeScript types, which is very handy:
import { z } from "zod";
const User = z.object({
username: z.string(),
});
User.parse({ username: "Ludwig" });
// extract the inferred type
type User = z.infer<typeof User>;
// { username: string }
See: https://github.com/colinhacks/zod#basic-usage
In my current project I had to import original Zod for that use case only. I don't like that, because I'm afraid, that nestjs-zod's createZodDto
on schemas created with original Zod may fail in the future due to compatibility issues. And it's also confusing for developers to have two variants of Zod in the same project.
Hi!
I tried to create a pr for this problem but I wasn't allowed so here it comes. You should change the date-string.ts regex to:
const formatToRegex: Record<DateStringFormat, RegExp> = { 'date': /^\d{4}-\d{2}-\d{2}$/, 'date-time': /^(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?([+-]\d{2}:\d{2}|Z))$/, }
and add a new test to line 60 for example
is('2017-08-20T20:00:00.000000Z', true)
Is there a way to create a DTO for the transformed output type of schema?
Let's say, this is the schema for the DB model:
const modelSchemaDb = z.object({
id: z.string(),
subItem: {
id: z.string(),
content: z.string(),
},
})
In my API I want want the nested DB record to be flattened in the response, so I add a transform to the schema:
const modelSchemaTransform = modelSchemaDb.transform((item) => ({
id: item.id,
content: item.subitem.content,
}))
Now I want to create a DTO class for the transformed output of the schema, but
class modelDtoTransform extends createZodDto(modelSchemaTransform) {}
creates the same thing as
class modelDtoDb extends createZodDto(modelSchemaDb) {}
A class matching the nested type:
{
id: string;
subItem: {
id: string;
content: string;
}
}
which leads to a wrong open api definition, in my case.
I need a DTO class matching the flattened type:
{
id: string;
content: string;
}
Is there already a way to achive this?
The date fields createdAt
and updatedAt
were not shown in the Swagger doc.
export const WorkSchema = z.object({
id: z.string().uuid(),
title: z.string(),
createdAt: z.coerce.date(),
updatedAt: z.coerce.date(),
})
The reason was the version of swagger-ui-express
.
Updating the module fixed the issue.
- "swagger-ui-express": "^4.1.4",
+ "swagger-ui-express": "^4.6.3",
I encountered an issue that involves TypeScript type inference and named exports. The problem arises when attempting to export a variable created via zodToOpenAPI
, which leads to an error related to the ExtendedSchemaObject
import { createZodDto, zodToOpenAPI } from 'nestjs-zod';
import { z } from 'nestjs-zod/z';
export const EnvConfigSchema = z.object({
PORT: z.coerce.number().int().positive().describe('Port number'),
});
export const EnvConfigApi = zodToOpenAPI(EnvConfigSchema);
Typescript intellisense should not scream.
Exported variable EnvConfigApi
has or is using name ExtendedSchemaObject
from external module "node_modules/.pnpm/nestjs-zod@2.3.3_@nestjs+common@10.0.0_@nestjs+core@10.0.0_@nestjs[email protected][email protected]/node_modules/nestjs-zod/dist/index" but cannot be named.
The interface ExtendedSchemaObject
should be included in the module's exports.
This resolves the issue.
I'm using yarn berry and using nestjs-zod for validating dtos.
I want to import createZodDto in client-side without installing no server-side library.
but i can't because createZodDto is imported from index.ts which imports other server-side dependent files.
I suggest it should be in other folder, where server-side library is not related with.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.