Skip to content

Commit 9d8434a

Browse files
author
Evgeny Zakharov
authored
Merge pull request #46 from austinwoon/chore/export-zod-serializer
2 parents 99d7675 + 2038e9d commit 9d8434a

File tree

2 files changed

+86
-34
lines changed

2 files changed

+86
-34
lines changed

README.md

+85-34
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,10 @@
2323

2424
## Ecosystem
2525

26-
| Package | About |
27-
| :-- | :-- |
28-
| [nestjs-zod](https://github.com/risenforces/nestjs-zod) | A tools for integrating Zod into your NestJS application |
29-
| [nestjs-zod-prisma](https://github.com/risenforces/nestjs-zod-prisma) | Generate Zod schemas from your Prisma schema |
26+
| Package | About |
27+
| :-------------------------------------------------------------------- | :------------------------------------------------------- |
28+
| [nestjs-zod](https://github.com/risenforces/nestjs-zod) | A tools for integrating Zod into your NestJS application |
29+
| [nestjs-zod-prisma](https://github.com/risenforces/nestjs-zod-prisma) | Generate Zod schemas from your Prisma schema |
3030

3131
## Core library features
3232

@@ -54,10 +54,11 @@ yarn add nestjs-zod zod
5454
```
5555

5656
Peer dependencies:
57+
5758
- `zod` - `>= 3.14.3`
58-
- `@nestjs/common` - `>= 8.0.0` (required on server side)
59-
- `@nestjs/core` - `>= 8.0.0` (required on server side)
60-
- `@nestjs/swagger` - `>= 5.0.0` (only when using `patchNestJsSwagger`)
59+
- `@nestjs/common` - `>= 8.0.0` (required on server side)
60+
- `@nestjs/core` - `>= 8.0.0` (required on server side)
61+
- `@nestjs/swagger` - `>= 5.0.0` (only when using `patchNestJsSwagger`)
6162

6263
All peer dependencies are marked as optional for better client side usage, but you need to install required ones when using `nestjs-zod` on server side.
6364

@@ -73,6 +74,7 @@ All peer dependencies are marked as optional for better client side usage, but y
7374
- [Using ZodGuard](#using-zodguard)
7475
- [Creating custom guard](#creating-custom-guard)
7576
- [Validation Exceptions](#validation-exceptions)
77+
- [Using ZodSerializerInterceptor](#using-zodserializerinterceptor-for-output-validation)
7678
- [Extended Zod](#extended-zod)
7779
- [ZodDateString](#zoddatestring)
7880
- [ZodPassword](#zodpassword)
@@ -103,7 +105,7 @@ const CredentialsSchema = z.object({
103105
Zod's classes and types are re-exported too, but under `/z` scope for more clarity:
104106

105107
```ts
106-
import { ZodString, ZodError, ZodIssue } from 'nestjs-zod/z'
108+
import { ZodString, ZodError, ZodIssue } from 'nestjs-zod/z'
107109
```
108110

109111
## Creating DTO from Zod schema
@@ -124,6 +126,7 @@ class CredentialsDto extends createZodDto(CredentialsSchema) {}
124126
### Using DTO
125127

126128
DTO does two things:
129+
127130
- Provides a schema for `ZodValidationPipe`
128131
- Provides a type from Zod schema for you
129132

@@ -218,7 +221,8 @@ import { createZodValidationPipe } from 'nestjs-zod'
218221

219222
const MyZodValidationPipe = createZodValidationPipe({
220223
// provide custom validation exception factory
221-
createValidationException: (error: ZodError) => new BadRequestException('Ooops')
224+
createValidationException: (error: ZodError) =>
225+
new BadRequestException('Ooops'),
222226
})
223227
```
224228

@@ -229,11 +233,13 @@ Sometimes, we need to validate user input before specific Guards. We can't use V
229233
The solution is `ZodGuard`. It works just like `ZodValidationPipe`, except for that is doesn't transform the input.
230234

231235
It has 2 syntax forms:
236+
232237
- `@UseGuards(new ZodGuard('body', CredentialsSchema))`
233238
- `@UseZodGuard('body', CredentialsSchema)`
234239

235240
Parameters:
236-
1. The source - `'body' | 'query' | 'params'`
241+
242+
1. The source - `'body' | 'query' | 'params'`
237243
2. Zod Schema or DTO (just like `ZodValidationPipe`)
238244

239245
When the data is invalid - it throws [ZodValidationException](#validation-exceptions).
@@ -261,7 +267,8 @@ import { createZodGuard } from 'nestjs-zod'
261267

262268
const MyZodGuard = createZodGuard({
263269
// provide custom validation exception factory
264-
createValidationException: (error: ZodError) => new BadRequestException('Ooops')
270+
createValidationException: (error: ZodError) =>
271+
new BadRequestException('Ooops'),
265272
})
266273
```
267274

@@ -274,7 +281,11 @@ import { validate } from 'nestjs-zod'
274281

275282
validate(wrongThing, UserDto, (zodError) => new MyException(zodError)) // throws MyException
276283

277-
const validatedUser = validate(user, UserDto, (zodError) => new MyException(zodError)) // returns typed value when succeed
284+
const validatedUser = validate(
285+
user,
286+
UserDto,
287+
(zodError) => new MyException(zodError)
288+
) // returns typed value when succeed
278289
```
279290

280291
## Validation Exceptions
@@ -301,6 +312,7 @@ The default server response on validation error looks like that:
301312
The reason of this structure is default `ZodValidationException`.
302313

303314
You can customize the exception by creating custom `nestjs-zod` entities using the factories:
315+
304316
- [Validation Pipe](#creating-custom-validation-pipe)
305317
- [Guard](#creating-custom-guard)
306318

@@ -321,6 +333,49 @@ export class ZodValidationExceptionFilter implements ExceptionFilter {
321333
}
322334
```
323335

336+
## Using ZodSerializerInterceptor for output validation
337+
338+
To ensure that a response conforms to a certain shape, you may use the `ZodSerializerInterceptor` interceptor.
339+
340+
This would be especially useful in prevent accidental data leaks.
341+
342+
This is similar to NestJs' `@ClassSerializerInterceptor` feature [here](https://docs.nestjs.com/techniques/serialization)
343+
344+
### Include `@ZodSerializerInterceptor` in application root
345+
346+
```ts
347+
@Module({
348+
...
349+
providers: [
350+
...,
351+
{ provide: APP_INTERCEPTOR, useClass: ZodSerializerInterceptor },
352+
],
353+
})
354+
export class AppModule {}
355+
```
356+
357+
### Use `@ZodResponseDto` to define the shape of the response for endpoint in controller
358+
359+
```ts
360+
const UserSchema = z.object({ username: string() })
361+
362+
export class UserDto extends createZodDto(UserSchema) {}
363+
```
364+
365+
```ts
366+
@Controller('user')
367+
export class UserController {
368+
constructor(private readonly userService: UserService) {}
369+
370+
@ZodResponseDto(UserDto)
371+
getUser(id: number) {
372+
return this.userService.findOne(id) // --> The native service method returns { username: string, password: string by default }
373+
}
374+
}
375+
```
376+
377+
In the above example, despite the `userService.findOne` method returns `password`, the `password` property will be stripped out thanks to the `@ZodResponseDto` decorator.
378+
324379
## Extended Zod
325380

326381
As you learned in [Writing Zod Schemas](#writing-zod-schemas) section, `nestjs-zod` provides a special version of Zod. It helps you to validate the user input more accurately by using our custom schemas and methods.
@@ -365,31 +420,37 @@ z.dateString().weekend()
365420
```
366421

367422
Valid `date` format examples:
423+
368424
- `2022-05-15`
369425

370426
Valid `date-time` format examples:
427+
371428
- `2022-05-02:08:33Z`
372429
- `2022-05-02:08:33.000Z`
373430
- `2022-05-02:08:33+00:00`
374431
- `2022-05-02:08:33-00:00`
375432
- `2022-05-02:08:33.000+00:00`
376433

377434
Errors:
435+
378436
- `invalid_date_string` - invalid date
379437

380438
- `invalid_date_string_format` - wrong format
381439

382440
Payload:
441+
383442
- `expected` - `'date' | 'date-time'`
384443

385444
- `invalid_date_string_direction` - not past/future
386445

387446
Payload:
447+
388448
- `expected` - `'past' | 'future'`
389449

390450
- `invalid_date_string_day` - not weekDay/weekend
391451

392452
Payload:
453+
393454
- `expected` - `'weekDay' | 'weekend'`
394455

395456
- `too_small` with `type === 'date_string_year'`
@@ -425,6 +486,7 @@ z.password().atLeastOne('special')
425486
```
426487

427488
Errors:
489+
428490
- `invalid_password_no_digit`
429491
- `invalid_password_no_lowercase`
430492
- `invalid_password_no_uppercase`
@@ -458,26 +520,24 @@ Therefore, the error details is located inside `params` property:
458520

459521
```ts
460522
const error = {
461-
"code": "custom",
462-
"message": "Invalid date, expected it to be the past",
463-
"params": {
464-
"isNestJsZod": true,
465-
"code": "invalid_date_string_direction",
523+
code: 'custom',
524+
message: 'Invalid date, expected it to be the past',
525+
params: {
526+
isNestJsZod: true,
527+
code: 'invalid_date_string_direction',
466528

467529
// payload is always located here in a flat view
468-
"expected": "past"
530+
expected: 'past',
469531
},
470-
"path": [
471-
"date"
472-
]
532+
path: ['date'],
473533
}
474534
```
475535

476536
### Working with errors on the client side
477537

478538
Optionally, you can install `nestjs-zod` on the client side.
479539

480-
The library provides you a `/frontend` scope, that can be used to detect custom NestJS Zod issues and process them the way you want.
540+
The library provides you a `/frontend` scope, that can be used to detect custom NestJS Zod issues and process them the way you want.
481541

482542
```ts
483543
import { isNestJsZodIssue, NestJsZodIssue, ZodIssue } from 'nestjs-zod/frontend'
@@ -498,6 +558,7 @@ function mapToFormErrors(issues: ZodIssue[]) {
498558
### Setup
499559

500560
Prerequisites:
561+
501562
- `@nestjs/swagger` with version `^5.0.0` installed
502563

503564
Apply a patch:
@@ -563,11 +624,7 @@ The output will be the following:
563624
"sex": {
564625
"description": "We respect your gender choice",
565626
"type": "string",
566-
"enum": [
567-
"male",
568-
"female",
569-
"nonbinary"
570-
]
627+
"enum": ["male", "female", "nonbinary"]
571628
},
572629
"social": {
573630
"type": "object",
@@ -581,13 +638,7 @@ The output will be the following:
581638
"format": "date-time"
582639
}
583640
},
584-
"required": [
585-
"username",
586-
"password",
587-
"sex",
588-
"social",
589-
"birthDate"
590-
]
641+
"required": ["username", "password", "sex", "social", "birthDate"]
591642
}
592643
```
593644

src/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,5 @@ export { ZodValidationException } from './exception'
44
export { createZodGuard, UseZodGuard, ZodGuard } from './guard'
55
export { patchNestJsSwagger, zodToOpenAPI } from './openapi'
66
export { createZodValidationPipe, ZodValidationPipe } from './pipe'
7+
export { ZodSerializerDto, ZodSerializerInterceptor } from './serializer'
78
export { validate } from './validate'

0 commit comments

Comments
 (0)