[NestJS 带你飞!] DAY09 - Pipe (上)

什麽是 Pipe?

Pipe 经常被用来处理使用者传入的参数,比如:验证参数的正确性、型别的转换等。它有点像是客人画完点餐单之後,服务生要进行点餐单的检查。

Nest Pipe

在 Nest 中,Pipe 支援 Exception 的错误处理机制,当在 Pipe 抛出 Exception 时,该次请求就 不会 进入到 Controller 对应的方法里,这样的设计方法能够有效隔离验证程序与主执行程序,是非常好的实作方式。
https://ithelp.ithome.com.tw/upload/images/20210324/20119338CqPFuiYMnl.png

Nest 内建了以下几个 Pipe 来辅助资料转型与验证:

  • ValidationPipe:验证资料格式的 Pipe。
  • ParseIntPipe:解析并验证是否为 Integer 的 Pipe。
  • ParseBoolPipe:解析并验证是否为 Boolean 的 Pipe。
  • ParseArrayPipe:解析并验证是否为 Array 的 Pipe。
  • ParseUUIDPipe:解析并验证是否为 UUID 格式的 Pipe。
  • DefaultValuePipe:验证资料格式的 Pipe。

使用 Pipe

Pipe 的使用方式很简单,假设要解析并验证路由参数是否为 Integer 的话,只需要在 @Param 装饰器填入路由参数名称并带入 ParseIntPipe 即可。以 app.controller.ts 为例,如果 id 解析後为数字,就会透过 AppService 去取得对应的 User 资讯,否则会抛出 Exception:

import { Controller, Get, Param, ParseIntPipe } from '@nestjs/common';
import { AppService } from './app.service';

@Controller()
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Get(':id')
  getUser(@Param('id', ParseIntPipe) id: number) {
    return this.appService.getUser(id);
  }

}

调整 app.service.ts

import { Injectable } from '@nestjs/common';

@Injectable()
export class AppService {

  getUser(id: number) {
    const users = [
      {
        id: 1,
        name: 'HAO'
      }
    ];
    const user = users.find(x => x.id === id);
    return user || {};
  }
}

透过浏览器查看 http://localhost:3000/HAO 会收到错误讯息,因为路由参数为 HAO,并不能解析为 Integer

{
  "statusCode": 400,
  "message": "Validation failed (numeric string is expected)",
  "error": "Bad Request"
}

内建 Pipe 自订 HttpCode

假设想要更改错误讯息,那 ParseIntPipe 就必须实例化并带入相关参数,以 app.controller.ts 为例,我希望出错时收到的 HttpCode 是 406

import { Controller, Get, HttpStatus, Param, ParseIntPipe } from '@nestjs/common';
import { AppService } from './app.service';

@Controller()
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Get(':id')
  getUser(
    @Param('id', new ParseIntPipe({ errorHttpStatusCode: HttpStatus.NOT_ACCEPTABLE }))
    id: number
  ) {
    return this.appService.getUser(id);
  }

}

透过浏览器查看 http://localhost:3000/HAO 会得到下方结果:

{
  "statusCode": 406,
  "message": "Validation failed (numeric string is expected)",
  "error": "Not Acceptable"
}

内建 Pipe 自订 Exception

如果想要自订错误讯息的话,可以使用 exceptionFactory 这个参数来指定产生的 Exception。以 app.controller.ts 为例:

import { Controller, Get, NotAcceptableException, Param, ParseIntPipe } from '@nestjs/common';
import { AppService } from './app.service';

@Controller()
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Get(':id')
  getUser(
    @Param(
      'id',
      new ParseIntPipe({
        exceptionFactory: () => new NotAcceptableException('无法解析为数字')
      })
    )
    id: number
  ) {
    return this.appService.getUser(id);
  }

}

透过浏览器查看 http://localhost:3000/HAO 会得到下方结果:

{
  "statusCode": 406,
  "message": "无法解析为数字",
  "error": "Not Acceptable"
}

自订 Pipe

如果觉得内建的 Pipe 无法满足需求的话,Nest 是可以自订 Pipe 的,事实上,Pipe 就是一个带有 @Injectableclass,不过它要去实作 PipeTransform 这个介面。Pipe 可以透过 CLI 产生:

$ nest generate pipe <PIPE_NAME>

注意<PIPE_NAME> 可以含有路径,如:pipes/parse-int,这样就会在 src 资料夹下建立该路径并含有 Pipe。

这边我建立一个 ParseIntPipepipes 资料夹下:

$ nest generate pipe pipes/parse-int

src 底下会看见一个名为 pipes 的资料夹,里面有 parse-int.pipe.ts 以及 parse-int.pipe.spec.ts
https://ithelp.ithome.com.tw/upload/images/20210324/20119338MBYJXIgGov.png

下方为 Pipe 的骨架,会看到有一个 transform(value: any, metadata: ArgumentMetadata) 方法,这就是要做逻辑判断的地方,其中,value 为传进来的值,metadata 为当前正在处理的参数元数据:

import { ArgumentMetadata, Injectable, PipeTransform } from '@nestjs/common';

@Injectable()
export class ParseIntPipe implements PipeTransform {
  transform(value: any, metadata: ArgumentMetadata) {
    return value;
  }
}

注意PipeTransform 後面可以添加两个 Type,第一个为 T,定义传入的值应该为何种型别,也就是 transform 里面的 value,第二个为 R,定义回传的资料型别。

这里我们调整一下 parse-int.pipe.ts,经过 parseInt 之後的 value 是否为 NaN,如果是则会抛出 NotAcceptableException

import { ArgumentMetadata, Injectable, NotAcceptableException, PipeTransform } from '@nestjs/common';

@Injectable()
export class ParseIntPipe implements PipeTransform<string, number> {
  transform(value: string, metadata: ArgumentMetadata) {
    const integer = parseInt(value);
    if ( isNaN(integer) ) {
      throw new NotAcceptableException('无法解析为数字');
    }
    return integer;
  }
}

接着去修改 app.controller.ts,来套用看看自己设计的 ParseIntPipe

import { Controller, Get, Param } from '@nestjs/common';
import { AppService } from './app.service';
import { ParseIntPipe } from './pipes/parse-int.pipe';

@Controller()
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Get(':id')
  getUser(
    @Param('id', ParseIntPipe) id: number
  ) {
    return this.appService.getUser(id)
  }

}

透过浏览器查看 http://localhost:3000/HAO 会得到下方结果:

{
  "statusCode": 406,
  "message": "无法解析为数字",
  "error": "Not Acceptable"
}

小结

Pipe 在资料验证这块是非常实用的功能,不过如果有物件类型的资料要如何验证呢?这部分我留到下篇再详细说明。附上今天的懒人包:

  1. Pipe 经常用在资料验证与型别转换。
  2. Nest 有内建六个 Pipe。
  3. 内建 Pipe 可以自订 HttpCode 或 Exception。
  4. Pipe 就是一个带有 @Injectableclass,它要去实作 PipeTransform 这个介面。

<<:  [Day24] 第一个 Angular App

>>:  Parser Generator (一)

JAVA - Windows 10 安装 Maven

JAVA - Windows 10 安装 Maven 参考资料 参考:(一)maven 新手教学: ...

Day12 NodeJS-Web Server I

今天的目标是:用NodeJS练习写一个简单的Web Server,所以会先介绍一下NodeJS中的h...

django入门(七) — 简单范例(5)-Django ORM操作

Django ORM介绍 一般而言,我们要存取资料库需要透过SQL语法,但在django则是使用OR...

2021-11-24 盘势分析

加权指数 完成W底後,在10/19站上颈线,直接一路狂奔直到11/19,历经1个月的多头格局, 在这...

连续 30 天 玩玩看 ProtoPie - Day 6

终於要从 Beginner 迈向 Intermediate 了。 这次的讲者讲话好清楚,转 1.75...