Pipes 是帶有@Pipe()裝飾器的class,Pipe必須實作PipeTransform介面。
Pipe可以將input data轉換成我們想要的output data,它也可以扛下參數資料驗證工作,在參數資料不正確時拋錯出來,這個錯誤會被上一章節提到的ExceptionsHandler或自定義的Exception Filter所捕捉。
cd src/modules/Shared & mkdir Pipes
import { PipeTransform, Pipe, ArgumentMetadata } from '@nestjs/common';
//Pipe裝飾器,會告訴nestjs這是Pipe
@Pipe()
//要實作PipeTransform的介面
export class ValidationPipe implements PipeTransform<any>{
//PipeTransform介面有transform(),這可以轉換input data並回傳
transform(value: any, metadata: ArgumentMetadata) {
//在這邊先不做任何轉換,只是要來看看Pipe的風貌,所以直接回傳value
return value;
}
}
注意:ValidationPipe 只能run在TypeScript,nestjs作者表示,如果是使用一般JavaScript的話,推薦使用Joi套件。
export interface ArgumentMetadata {
type: Paramtype;
metatype?: new (...args) => any;
data?: string;
}
Paramtype
export declare type Paramtype = 'body' | 'query' | 'param' | 'custom';
官網說明:
注意:因為TypeScript的Interface在轉譯過程會直接消失,所以如果是使用interface而不是class的話,metatype的型別會變成Object。
@Post('users')
async addUser( @Response() res, @Body() createUserDTO: CreateUserDTO) {
//使用Rx.js,所以回傳可以做更多資料流的處理
await this.userService.addUser(createUserDTO).subscribe((users) => {
res.status(HttpStatus.OK).json(users);
})
}
src/modules/Users/DTO/create-users.dto.ts
export class CreateUserDTO {
readonly _id: number;
readonly _name: string;
readonly _age: number;
}
import { IsString, IsInt } from 'class-validator';
export class CreateUserDTO {
@IsInt()
readonly _id: number;
@IsString()
readonly _name: string;
@IsInt()
readonly _age: number;
}
import { HttpException } from '@nestjs/core';
import { PipeTransform, Pipe, ArgumentMetadata, HttpStatus } from '@nestjs/common';
import { validate } from 'class-validator';
import { plainToClass } from 'class-transformer';
//Pipe裝飾器,會告訴nestjs這是Pipe
@Pipe()
//要實作PipeTransform的介面
export class ValidationPipe implements PipeTransform<any>{
//PipeTransform介面有transform(),這可以轉換input data並回傳
async transform(value, metadata: ArgumentMetadata) {
const { metatype } = metadata;
/*不檢查原生JavaScript的型別,因為刻意寫ValidationPipe,就是要使用自定義的DTO class
的屬性去做參數型別檢查,如果metatype是原生JavaScript的型別,就直接return 原始參數,
不做ValidationPipe的檢查。
*/
if (!metatype || !this.toValidate(metatype)) {
return value;
}
//這裡使用class-transformer的方法,將plain javascript object(像是JSON object),轉換成一個class的object。
const object = plainToClass(metatype, value);
//在CreateUserDTO,我們有使用class-validator的驗證裝飾器,validate()會回傳錯誤陣列。
const errors = await validate(object);
if (errors.length > 0) {
console.log('validate errors',errors);
throw new HttpException('檢驗錯誤', HttpStatus.BAD_REQUEST);
}
//ValidationPipe是特別打造出來檢驗型別用,最後要回傳原始參數,避免覆寫。
return value;
}
//檢驗是否為原生JavaScript的型別
private toValidate(metatype): boolean {
const types = [String, Boolean, Number, Array, Object];
return !types.find((type) => metatype === type);
}
}
說明:請看程式碼裏頭的註解。
@Post('users')
async addUser( @Response() res, @Body(new ValidationPipe()) createUserDTO: CreateUserDTO) {
//使用Rx.js,所以回傳可以做更多資料流的處理
await this.userService.addUser(createUserDTO).subscribe((users) => {
res.status(HttpStatus.OK).json(users);
})
}
//@UseFilters(new HttpExceptionFilter())
src/modules/Users/users.controller.ts
//@UseFilters(new HttpExceptionFilter())
9. 來實際體驗一下ValidationPipe,打開Postman,對http://localhost:3000/users 進行POST請求,並觀察console畫面。
9.1 故意將_id傳一個字串。
說明:符合ValidationPipe所做的回應。
9.2 console結果
拜class-validator所賜console有顯示詳細的型別錯誤資訊。
9.3 打開Postman,對http://localhost:3000/users 進行POST請求,傳入符合CreateUserDTO型別的json object。
成功!
import { HttpException } from '@nestjs/core';
import { PipeTransform, Pipe, ArgumentMetadata, HttpStatus } from '@nestjs/common';
@Pipe()
export class ParseIntPipe implements PipeTransform<string>{
async transform(value: string, metadata: ArgumentMetadata) {
const val = parseInt(value, 10);
if (isNaN(val)) {
throw new HttpException('檢驗錯誤', HttpStatus.BAD_REQUEST);
}
return val;
}
}
import { ParseIntPipe } from '../Shared/Pipes/parse-int.pipe';
@Get('users/:id')
//使用Express的參數
//@Param('id')可以直接抓id參數
//使用ParseIntPipe
async getUser( @Response() res, @Param('id', new ParseIntPipe()) id) {
//id參數前面不帶加號,ParseIntPipe會將參數轉型為int型別
await this.userService.getUser(id)
.then((user) => {
res.status(HttpStatus.OK).json(user);
})
.catch((error) => {
console.error(error);
res.status(HttpStatus.INTERNAL_SERVER_ERROR);
})
}
正常顯示,所以ParseIntPipe轉換型別成功。
程式碼都在github
"TypeScript的Interface在轉譯成JavaScript時會變成class"
這一點我倒沒有注意到,我以為 interface 會直接消失。
Jonny Huang您說的是對的,我的解釋不好,剛剛修改了文章。
妳好,請問如何使用Pipe
進行部分參數驗證?
按照官方文檔的寫法,默認會對所有參數進行驗證,但是某些API不需要這樣做,例如更新和刪除API(只需要驗證部分參數)。
我有註意到 class-validator
自帶的 Skipping missing properties
,以及 typeorm
的 Entity Listeners and Subscribers
,但是我不知道怎麽用,請問能給我壹些幫助嗎,謝謝~!
抱歉來晚了xd
您當年要的資料如下~
另外在 dto 的 class-validaotr
有 IsOptional()
可以併用
@IsOptional()
@IsString()
readonly _id?: string;
@IsString()
@IsNotEmpty()
readonly content: string;
@IsInt()
readonly priority?: number;
Updated Date: 2022/04/27
版本更新
// package.json [dependencies]
"@nestjs/common": "^8.0.0",
// cli
$ nest --version
8.2.5
Point 2.
import { PipeTransform, Pipe, ArgumentMetadata } from '@nestjs/common';
@Pipe()
export...
其中的 Pipe
改為 Injectable
import { PipeTransform, Injectable, ArgumentMetadata } from '@nestjs/common';
@Injectable()
export...
Point 6.
簡化
// before
async transform(value, metadata: ArgumentMetadata) {
const { metatype } = metadata;
if ...
// after
async transform(value, { metatype }: ArgumentMetadata) {
if ...
補充:( 回應 raymonda
validate 可以帶入許多不同的 setting
// example
const errors = await validate(object, {
skipMissingProperties: true,
forbidUnknownValues: true,
});