管道是具有@Injectable()装饰器的类。管道应实现PipeTransform接口
管道有两个功能:1. 验证。对输入数据进行验证,验证通过继续传递,否则抛出异常。2. 转化。将输入数据转化后输出。管道可以绑定在controller或其他任何方法上
内置管道
NestJS内置三个管道,即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表示value的类型,R是transform()方法的返回类型。
每个管道必须提供transform()方法。这个方法接收两个参数:
- value:当前处理的参数
- metadata:元数据,元数据包含一些属性,
export interface ArgumentMetadata {
readonly type: 'body' | 'query' | 'param' | 'custom';
readonly metatype?: Type;
readonly data?: string;
}
参数 | 描述 |
---|---|
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正确执行,需要验证CreateCatDTO中的三个属性,这个工作可以交给管道来完成
基于结构的验证
Joi是JavaScript最强大的模式描述语言和数据验证器
$ 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 的构造函数接受一个 ObjectSchema 类型的对象,通过调用该对象的 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()装饰器创建管道实例,并将其传递给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;
}
接来下可以构建我们的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 {
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);
}
在方法级别设置管道需要使用 UsePipes() 装饰器,该装饰器接受一个Pipe实例,也可以传递一个类,有框架来实例化,如下所示:
@Post()
@UsePipes(ValidationPipe)
async create(@Body() createCatDto: CreateCatDto) {
this.catsService.create(createCatDto);
}
参数级别管道
可以为某个参数单独设置管道
@Post()
async create(@Body(new ValidationPipe()) createCatDto: CreateCatDto) {
this.catsService.create(createCatDto);
}
全局管道
方法一:修改main.ts
app.useGlobalPipes(new ValidationPipe());用于设置全局管道,这种方式适用于不使用任何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 {}
转换管道
一般在以下情形使用转换管道,1. 需要对输入数据进行处理 2. 需要使用默认参数
parse-int.pipe.ts
import { PipeTransform, Injectable, ArgumentMetadata, BadRequestException } from '@nestjs/common';
@Injectable()
export class ParseIntPipe implements PipeTransform {
transform(value: string, metadata: ArgumentMetadata): number {
const val = parseInt(value, 10);
if (isNaN(val)) {
throw new BadRequestException('Validation failed');
}
return val;
}
}
上面的代码实现一个字符串转化为整数的管道
使用
@Get(':id')
async findOne(@Param('id', new ParseIntPipe()) id) {
return await this.catsService.findOne(id);
}
另一个有用的例子是按ID从数据库中选择一个现有的用户实体
@Get(':id')
findOne(@Param('id', UserByIdPipe) userEntity: UserEntity) {
return userEntity;
}
@Injectable()
export class ParseIntPipe implements PipeTransform {
transform(value: string, metadata: ArgumentMetadata): number {
const val = await this.usersService.findOne(id);
if (isNaN(val)) {
throw new BadRequestException('Validation failed');
}
return val;
}
}
内置验证管道
Nest提供许多管道,可以查看这里https://docs.nestjs.com/techniques/validation,部分管道同时支持验证和转化,如ValidationPipe
@Post()
@UsePipes(new ValidationPipe({ transform: true }))
async create(@Body() createCatDto: CreateCatDto) {
this.catsService.create(createCatDto);
}
{ transform: true } Pipe会返回转化后结果