一篇文章学会如何使用 NestJS 的 管道Pipes 实现高效的数据转换和验证

前言

如果你是一个正在学习NestJS的开发者,那么这篇文章将会为你展示如何通过管道来进行数据验证和转换,你会发现这是一个强大的工具,能够极大地提高你的开发效率。对于已经熟悉NestJS的开发者,本文的内容也会帮助你更好地理解和应用管道的概念。

什么 是 NestJS 的管道(Pipes)

NestJS管道(Pipes)是NestJS框架的一部分,它主要是用于处理和解析来自客户端的输入数据,然后将数据传递给请求处理器或下一个管道。在NestJS中,管道主要可以用于两种情况:数据转换和验证。

NestJS 的管道(Pipes)的使用场景

  • 数据转换

    管道也可以用来转换输入数据。例如,可以把字符串类型的数据转换为数字或者日期对象。

  • 默认值设定

    在某些情况下,我们可能希望为某些字段提供默认值。例如,用户可选地提供一个“页数”字段来控制分页查询的返回数据,但如果用户没有提供这个字段,我们可能需要设置一个默认值。这种情况下,管道可以在输入数据中插入缺失的默认值。

  • 数据验证

    管道可以用来验证传入的请求数据,以确保它符合应用的期望需求。如果数据无效或缺失,管道可以返回一个错误。

  • 业务规则验证

    除了内建的验证装饰器,如**@IsString() @IsNotEmpty()**等,我们也可以在管道中实现更复杂的自定义验证逻辑。例如,我们可能需要确保用户名在系统中是唯一的。

  • 复杂对象装配

    在某些情况下,我们可能需要对输入数据进行复杂的处理,以构建出一个复杂的对象。在这种场景下,管道可以被用来应对这种复杂的逻辑。

如何使用 NestJS 的管道(Pipes)

场景一:数据验证

现在假设我们在开发一个博客系统,我们需要验证从客户端传来的文章的标题和内容是否合格。我们可以创建一个管道实现这个功能。

首先,我们需要安装 class-validatorclass-transformer这两个包。

shell
$ npm i --save class-validator class-transformer

然后我们创建一个DTO(Data Transfer Object)来规定文章的数据结构:

javascript
import { IsString, IsNotEmpty } from 'class-validator'; export class CreatePostDto { @IsString() @IsNotEmpty() readonly title: string; @IsString() @IsNotEmpty() readonly content: string; }

在这里,我们使用了 @IsString()@IsNotEmpty()装饰器来指定规则。然后,我们将此DTO作为参数传递给控制器:

javascript
import { Body, Post, Controller, UsePipes, ValidationPipe } from '@nestjs/common'; import { CreatePostDto } from './create-post.dto'; @Controller('posts') export class PostsController { @Post() @UsePipes(ValidationPipe) create(@Body() createPostDto: CreatePostDto) { // ...处理创建文章的逻辑 } }

在这个例子中,我们通过在 @Post()装饰器方法上使用 @UsePipes(new ValidationPipe())装饰器来应用管道。

场景二:数据转换

让我们假设您希望将数字类型的字符串转换为真正的数字。我们可以创建一个管道来实现这个需求。

javascript
import { PipeTransform, Injectable, ArgumentMetadata } from '@nestjs/common'; @Injectable() export class ParseIntPipe implements PipeTransform { transform(value: any, metadata: ArgumentMetadata) { const val = parseInt(value, 10); if (isNaN(val)) { throw new Error('Validation failed'); } return val; } }

然后在控制器中使用这个管道:

javascript
import { Controller, Get, Param, ParseIntPipe } from '@nestjs/common'; @Controller('cats') export class CatsController { @Get(':id') findOne(@Param('id', ParseIntPipe) id: number) { // id现在是个数字,而非字符串 // ...处理具体的查询逻辑 } }

希望这篇文章能帮你理解NestJS的管道(Pipes)以及如何使用它们。在结构化编程和编写可维护代码方面,NestJs的管道可发挥重要作用。继续探索并学习这方面的知识将使你受益良多。

场景三:默认值设定

我们希望在用户进行分页查询时,如果他们没有提供“页数”信息,系统能自动设定一个默认值。可以创建一个管道来实现这个需求。

javascript
import { PipeTransform, Injectable, ArgumentMetadata } from '@nestjs/common'; @Injectable() export class OptionalParseIntPipe implements PipeTransform { private readonly defaultValue: number; constructor(defaultValue: number) { this.defaultValue = defaultValue; } transform(value: any, metadata: ArgumentMetadata) { const val = parseInt(value, 10); if (isNaN(val)) { return this.defaultValue; } return val; } }

然后在控制器中使用这个管道:

javascript
import { Controller, Get, Query, DefaultValuePipe } from '@nestjs/common'; @Controller('posts') export class PostsController { @Get() fetch(@Query('page', new OptionalParseIntPipe(1)) page: number) { // 如果用户没提供page参数,page的默认值就是1 // ...处理具体的查询逻辑 } }

场景四:业务规则验证

我们需要确保用户注册时,所输入的用户名在系统中是唯一的。可以创建一个管道来实现这个需求。

首先创建一个 UserService用于查询用户名:

javascript
import { Injectable } from '@nestjs/common'; @Injectable() export class UserService { async isUsernameUnique(username: string): Promise<boolean> { // ... 这里应该查询数据库,返回一个Promise,表示用户名是否唯一 } }

然后创建一个用户名验证管道:

javascript
import { PipeTransform, Injectable, ArgumentMetadata, BadRequestException } from '@nestjs/common'; @Injectable() export class UniqueUsernamePipe implements PipeTransform { constructor(private readonly userService: UserService) {} async transform(value: any, metadata: ArgumentMetadata) { const isUnique = await this.userService.isUsernameUnique(value); if (!isUnique) { throw new BadRequestException('Username is already taken!'); } return value; } }

在控制器中应用这个管道:

javascript
import { Controller, Post, Body } from '@nestjs/common'; @Controller('users') export class UsersController { @Post('register') async register(@Body('username', UniqueUsernamePipe) username: string) { // ... 处理具体的注册逻辑 } }

场景五:复杂对象装配

假设我们在创建文章时,需要为文章提供一个作者信息。作者信息通过id从数据库中获取,我们可以创建一个管道来实现这个需求。

javascript
import { PipeTransform, Injectable, ArgumentMetadata, NotFoundException } from '@nestjs/common'; import { UsersService } from './users.service'; @Injectable() export class UserByIdPipe implements PipeTransform { constructor(private readonly usersService: UsersService) {} async transform(value: any, metadata: ArgumentMetadata) { const user = await this.usersService.findById(value); if (!user) { throw new NotFoundException(`User with id ${value} not found`); } return user; } }

然后在控制器中使用这个管道:

javascript
import { Controller, Post, Body } from '@nestjs/common'; @Controller('posts') export class PostsController { @Post() async create(@Body('authorId', UserByIdPipe) author: User) { // 现在,author 的值是一个用户对象,我们可以使用它来创建文章 // ... 处理创建文章的逻辑 } }

总结

至此,我们已经对NestJS的管道有了更为深入的理解,包括了它的定义、使用场景以及怎样在具体的场合中使用它。无论是对于数据验证还是数据转换,它都有非常显著的效果,并且为我们的开发过程提供了很大的便利。然而,以上只是初步应用,NestJs的管道的强大之处远不止于此。