管道是具有@Injectable()裝飾器的類听诸。管道應(yīng)實現(xiàn)PipeTransform接口
管道有兩個功能:1. 驗證桅狠。對輸入數(shù)據(jù)進行驗證截型,驗證通過繼續(xù)傳遞趴荸,否則拋出異常。2. 轉(zhuǎn)化宦焦。將輸入數(shù)據(jù)轉(zhuǎn)化后輸出发钝。管道可以綁定在controller或其他任何方法上
內(nèi)置管道
NestJS內(nèi)置三個管道,即ValidationPipe波闹、ParseIntPipe和ParseUUIDPipe酝豪。
shared/pipes/validation.pipe.ts
import { PipeTransform, Injectable, ArgumentMetadata } from '@nestjs/common';
@Injectable()
export class ValidationPipe implements PipeTransform {
transform(value: any, metadata: ArgumentMetadata) {
return value;
}
}
PipeTransform<T, R>是一個通用接口,其中T表示value的類型精堕,R是transform()方法的返回類型孵淘。
每個管道必須提供transform()方法。這個方法接收兩個參數(shù):
- value:當(dāng)前處理的參數(shù)
- metadata:元數(shù)據(jù)歹篓,元數(shù)據(jù)包含一些屬性瘫证,
export interface ArgumentMetadata {
readonly type: 'body' | 'query' | 'param' | 'custom';
readonly metatype?: Type<any>;
readonly data?: string;
}
參數(shù) | 描述 |
---|---|
type | 告訴我們該屬性是一個body @body揉阎、query @Query、param @Parem背捌、或者自定義屬性類型 |
matatype | 屬性元類型毙籽,如String |
data | 傳遞給裝飾器的字符串,如@Body('string') 中的'string' |
測試用例
cats.controller.ts
@Post()
async create(@Body() createCatDto: CreateCatDto) {
this.catsService.create(createCatDto);
}
create-cat-dto.ts
export class CreateCatDto {
readonly name: string;
readonly age: number;
readonly breed: string;
}
我們要確保create正確執(zhí)行毡庆,需要驗證CreateCatDTO中的三個屬性坑赡,這個工作可以交給管道來完成
基于結(jié)構(gòu)的驗證
Joi是JavaScript最強大的模式描述語言和數(shù)據(jù)驗證器
$ npm install --save @hapi/joi
$ npm install --save-dev @types/hapi__joi
import { PipeTransform, Injectable, ArgumentMetadata, BadRequestException } from '@nestjs/common';
import { ObjectSchema } from '@hapi/joi';
@Injectable()
export class JoiValidationPipe implements PipeTransform {
constructor(private readonly schema: ObjectSchema) {}
transform(value: any, metadata: ArgumentMetadata) {
const { error } = this.schema.validate(value);
if (error) {
throw new BadRequestException('Validation failed');
}
return value;
}
}
JoiValidationPipe 的構(gòu)造函數(shù)接受一個 ObjectSchema 類型的對象,通過調(diào)用該對象的 validate 方法來驗證 value
綁定管道
create-cat.schema.ts
import Joi = require("@hapi/joi");
export const createCatSchema = Joi.object({
name: Joi.string(),
age: Joi.number(),
breed: Joi.string()
})
綁定管道十分簡單么抗,使用@UsePipes()裝飾器創(chuàng)建管道實例毅否,并將其傳遞給Joi驗證
@Post()
@UsePipes(new JoiValidationPipe(createCatSchema))
async create(@Body() createCatDto: CreateCatDto) {
this.catsService.create(createCatDto);
}
基于裝飾器的驗證
$ npm i --save class-validator class-transformer
基于裝飾器的驗證通過在類中中添加一些裝飾器來達到驗證目的
create-cat.dto.ts
import { IsString, IsInt } from 'class-validator';
export class CreateCatDto {
@IsString()
readonly name: string;
@IsInt()
readonly age: number;
@IsString()
readonly breed: string;
}
接來下可以構(gòu)建我們的pipe類
validation.pipe.ts
import { PipeTransform, Injectable, ArgumentMetadata, BadRequestException } from '@nestjs/common';
import { validate } from 'class-validator';
import { plainToClass } from 'class-transformer';
@Injectable()
export class ValidationPipe implements PipeTransform<any> {
async transform(value: any, { metatype }: ArgumentMetadata) {
if (!metatype || !this.toValidate(metatype)) {
return value;
}
const object = plainToClass(metatype, value);
const errors = await validate(object);
if (errors.length > 0) {
throw new BadRequestException('Validation failed');
}
return value;
}
private toValidate(metatype: Function): boolean {
const types: Function[] = [String, Boolean, Number, Array, Object];
return !types.includes(metatype);
}
}
使用管道
方法級別管道
cat.controller.ts
@Post()
@UsePipes(new ValidationPipe())
async create(@Body() createCatDto: CreateCatDto) {
this.catsService.create(createCatDto);
}
在方法級別設(shè)置管道需要使用 UsePipes() 裝飾器,該裝飾器接受一個Pipe實例蝇刀,也可以傳遞一個類搀突,有框架來實例化,如下所示:
@Post()
@UsePipes(ValidationPipe)
async create(@Body() createCatDto: CreateCatDto) {
this.catsService.create(createCatDto);
}
參數(shù)級別管道
可以為某個參數(shù)單獨設(shè)置管道
@Post()
async create(@Body(new ValidationPipe()) createCatDto: CreateCatDto) {
this.catsService.create(createCatDto);
}
全局管道
方法一:修改main.ts
app.useGlobalPipes(new ValidationPipe());用于設(shè)置全局管道熊泵,這種方式適用于不使用任何module提供的方法的情況仰迁,因為它本身不屬于任何一個module,要解決這個問題顽分,可以用第二種方式
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(new ValidationPipe());
await app.listen(3000);
}
bootstrap();
方法二:修改app.module.ts
import { Module } from '@nestjs/common';
import { APP_PIPE } from '@nestjs/core';
@Module({
providers: [
{
provide: APP_PIPE,
useClass: ValidationPipe
}
]
})
export class AppModule {}
轉(zhuǎn)換管道
一般在以下情形使用轉(zhuǎn)換管道徐许,1. 需要對輸入數(shù)據(jù)進行處理 2. 需要使用默認(rèn)參數(shù)
parse-int.pipe.ts
import { PipeTransform, Injectable, ArgumentMetadata, BadRequestException } from '@nestjs/common';
@Injectable()
export class ParseIntPipe implements PipeTransform<string, number> {
transform(value: string, metadata: ArgumentMetadata): number {
const val = parseInt(value, 10);
if (isNaN(val)) {
throw new BadRequestException('Validation failed');
}
return val;
}
}
上面的代碼實現(xiàn)一個字符串轉(zhuǎn)化為整數(shù)的管道
使用
@Get(':id')
async findOne(@Param('id', new ParseIntPipe()) id) {
return await this.catsService.findOne(id);
}
另一個有用的例子是按ID從數(shù)據(jù)庫中選擇一個現(xiàn)有的用戶實體
@Get(':id')
findOne(@Param('id', UserByIdPipe) userEntity: UserEntity) {
return userEntity;
}
@Injectable()
export class ParseIntPipe implements PipeTransform<string, UserEntity> {
transform(value: string, metadata: ArgumentMetadata): number {
const val = await this.usersService.findOne(id);
if (isNaN(val)) {
throw new BadRequestException('Validation failed');
}
return val;
}
}
內(nèi)置驗證管道
Nest提供許多管道,可以查看這里https://docs.nestjs.com/techniques/validation卒蘸,部分管道同時支持驗證和轉(zhuǎn)化雌隅,如ValidationPipe
@Post()
@UsePipes(new ValidationPipe({ transform: true }))
async create(@Body() createCatDto: CreateCatDto) {
this.catsService.create(createCatDto);
}
{ transform: true } Pipe會返回轉(zhuǎn)化后結(jié)果