前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >NestJs 管道(Pipe)

NestJs 管道(Pipe)

作者头像
前端小鑫同学
发布2023-10-16 15:21:09
2480
发布2023-10-16 15:21:09
举报

🎄Hi~ 大家好,我是小鑫同学,资深 IT 从业者,InfoQ 的签约作者,擅长前端开发并在这一领域有多年的经验,致力于分享我在技术方面的见解和心得

Nestjs 中管道是具有 @Injectable() 装饰器且已实现 PipeTransform 接口的类。

管道(Pipe)的作用

管道(Pipe)作用在每个控制器的处理方法上,也就是当每一个请求被路由到具体的控制器的方法后会先通过管道(Pipe)对传入的请求参数进行 转换验证,保证数据在被正式处理前是完全合法的。

管道(Pipe)的使用

Nestjs 中内置了下列的9个管道,利用这些管道可以轻松的验证路由参数、查询参数和请求正文是否合法,下面通过两个例子一起看一下管道的使用。

ParseIntPipe

ParseFloatPipe

ParseBoolPipe

ParseArrayPipe

ParseUUIDPipe

ParseEnumPipe

ParseFilePipe

DefaultValuePipe

ValidationPipe

findUserById 是用来根据用户 ID 获取用户信息的处理函数,期望id由客户端传来的必须是数字类型。

代码语言:javascript
复制
@Controller('users')
export class UsersController {
  @Get(':id')
  findUserById(@Param('id') id: number): string {
    return `The ID of this user is ${id}`;
  }
}

现在由于缺少对路由参数类型的校验,此时客户端在传递非数字类型的ID时并不会收到合理的提醒,这样很容易造成服务端业务逻辑的异常,有入库的操作的话还会造成垃圾数据。所以可将 ParseIntPipe 管道类直接添加到 @Param() 装饰器的第二位参数,如下图:

代码语言:javascript
复制
@Controller('users')
export class UsersController {
  @Get(':id')
  findUserById(@Param('id', ParseIntPipe) id: number): string {
    return `The ID of this user is ${id}`;
  }
}

增加 ParseIntPipe 管道的限制后,当客户端再次传递非数字类型的ID时就会收到对应的提示。

上面的例子中使用了管道类而非管道的实例是因为 Nestjs 基于 IoC 的设计在框架内部可以自动对类进行实例化操作,管道同时也支持通过构造函数传递选项的方式自定义内置管道的行为。

下面这个 findUserByUUID 函数中使用的 ParseUUIDPipe 管道默认情况下是支持接收不同版本的 UUID 的,但在例子中我们限制只可以接收 v5 版本的 UUID,就需要实例化 ParseUUIDPipe 并在构造函数中指定具体的 version

代码语言:javascript
复制
@Get(':uuid')
findUserByUUID(
    @Param('uuid', new ParseUUIDPipe({ version: '5' })) uuid: string,
): string {
    return `The UUID of this user is ${uuid}`;
}

基于 schema 的验证

createUser 处理函数中要求客户端传递一份包含 nameagegender 的数据,对于这种复杂的数据结构来说可以引入 schema (前端表单校验常用技术)来配合自定义管道实现。

代码语言:javascript
复制
export class CreateUserDto {
  name: string;
  age: number;
  gender: boolean;
}

@Post()
createUser(@Body() createUserDto: CreateUserDto): string {
  return `${createUserDto.name} is the 100th user`;
}

首先需要引入 joi 模块和 @types/joi 模块,使用 ES 模块导入的方式导入 joi 时需要在 tsconfig.json 中启用 esModuleInterop 选项。接着使用 Joi 模块将 CreateUserDto 中的三个属性均设置为必填项。

代码语言:javascript
复制
import Joi from 'joi';

export const createUserSchema = Joi.object({
  name: Joi.string().required(),
  age: Joi.number().required(),
  gender: Joi.bool().required(),
});

定义完 schema 后可以使用 nest g pi joi-validation 创建一个公共的管道,在 transform 函数中使用已经注入的ObjectSchema 对象提供的 validate 函数对请求参数 value 做验证,当验证不通过是抛出合理的异常,反之通过。

代码语言:javascript
复制
@Injectable()
export class JoiValidationPipe implements PipeTransform {
  constructor(private schema: ObjectSchema) {}

  transform(value: any, metadata: ArgumentMetadata) {
    const { error } = this.schema.validate(value);
    if (error) {
      throw new BadRequestException('Validation failed');
    }
    return value;
  }
}

这里的管道就需要绑定到 createUser 处理函数级别了,需要用到 @UsePipes() 装饰器,并传入通过 Joi 定义的 schema

代码语言:javascript
复制
@Post()
@UsePipes(new JoiValidationPipe(createUserSchema))
createUser(@Body() createUserDto: CreateUserDto): string {
  return `${createUserDto.name} is the 100th user`;
}

当客户端未传递其中某一个字段时就会收到如下的提示信息。

基于 dto 的验证

在基于 schema 的验证中不仅编写了通用的 joi-validation 管道,还用 Joi 库编写了一份和 CreateUserDto 几乎一样的 schema 文件,每当 DTO 文件有变更时就需要同步维护 schema 文件。

基于 dto 的验证就可以利用为已创建的 CreateUserDto 增加验证相关的装饰器并配合通过的管道即可完成,从而可以少维护一份文件,避免不一致造成的问题。

首先执行 npm i --save class-validator class-transformer 安装必要的模块,接着为 CreateUserDto 增加验证相关的装饰器。

代码语言:javascript
复制
import { IsString, IsNumber, IsBoolean, IsNotEmpty } from 'class-validator';

export class CreateUserDto {
  @IsString()
  @IsNotEmpty()
  name: string;

  @IsNumber()
  @IsNotEmpty()
  age: number;

  @IsBoolean()
  @IsNotEmpty()
  gender: boolean;
}

接着执行 nest g pi dto-validation 创建一个公共的管道,在这个管道中需要做这么几件事情:

  1. 解构 metadata 参数,获取请求体参数的元类型。
  2. 定义私有函数 toValidation,跳过非DTO的类型(非Javascript原类型)。
  3. 使用 plainToInstance 将元类型和请求体参数转为可验证的类型对象。
  4. 通过 validate 函数执行校验,校验未通过则抛出合理的异常信息。
代码语言:javascript
复制
import {
  ArgumentMetadata,
  BadRequestException,
  Injectable,
  PipeTransform,
} from '@nestjs/common';
import { validate } from 'class-validator';
import { plainToInstance } from 'class-transformer';

@Injectable()
export class DtoValidationPipe implements PipeTransform {
  async transform(value: any, metadata: ArgumentMetadata) {
    const { metatype } = metadata;
    if (!metatype || !this.toValidation(metatype)) {
      return value;
    }
    const object = plainToInstance(metatype, value);
    const errors = await validate(object);
    if (errors.length > 0) {
      throw new BadRequestException('Validation failed');
    }
    return value;
  }

  /**
   * 当 metatype 所指的参数的元类型仅为Javascript原生类型的话则跳过校验,这里只关注了对定义的DTO的校验
   */
  private toValidation(metatype: Function): boolean {
    const types: Function[] = [String, Boolean, Number, Array, Object];
    return !types.includes(metatype);
  }
}

再接着将 DtoValidationPipe 管道绑定到 createUser 处理方法并作验证。

代码语言:javascript
复制
@Post()
createUser(
  @Body(new DtoValidationPipe()) createUserDto: CreateUserDto,
): string {
  return `${createUserDto.name} is the 100th user`;
}

PS:Nestjs 提供的 ValidationPipe 管道可以完全支持上述两种验证方式,我们不必为自定义验证管道花费时间。

提供默认值

提供默认值可以看做是管道在转换场景的一个体现,增加默认值的处理可以使得服务端的代码更加的健壮。这里使用到了内置的 DefaultValuePipe 管道。

代码语言:javascript
复制
@Get()
findAllUsers(
  @Query('activeOnly', new DefaultValuePipe(false), ParseBoolPipe)
  activeOnly: boolean,
  @Query('page', new DefaultValuePipe(10), ParseIntPipe) page: number,
): string {
  return `This action return all users,request parameters:activeOnly: ${activeOnly},page:${page}`;
}

全局管道注册

除上述管道的注册位置,还支持全局注册,注册方式同全局异常过滤器的注册,一个是基于 app 实例的注册,另一个是基础跟模块的注册。

代码语言:javascript
复制
async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useGlobalPipes(new ValidationPipe());
  await app.listen(3000);
}
bootstrap();
代码语言:javascript
复制
import { Module } from '@nestjs/common';
import { APP_PIPE } from '@nestjs/core';

@Module({
  providers: [
    {
      provide: APP_PIPE,
      useClass: ValidationPipe
    }
  ]
})
export class AppModule {}

总结

以上就是 Nest 中管道类的使用方式,也是保证参数正常接收、正常入库的必要手段。


如果看完觉得有收获,欢迎点赞、评论、分享支持一下。你的支持和肯定,是我坚持写作的动力~

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2023-10-11,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 管道(Pipe)的作用
  • 管道(Pipe)的使用
  • 基于 schema 的验证
  • 基于 dto 的验证
  • 提供默认值
  • 全局管道注册
  • 总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档