Skip to main content

pipe (09,10)

什麼是 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) { // 使用 pipe
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 這個介面。

遇到物件格式的資料要如何做驗證這個問題,事實上這個解法只需要使用 DTO、ValidationPipeclass-validator 以及 class-transformer ,這裡先完成簡單的前置作業,透過 npm 安裝 class-validatorclass-transformer

$ npm install --save class-validator class-transformer

⭐️ 透過 class 去作資料驗証,看起來非常的方便

DTO 格式驗證

為了模擬驗證機制,這裡先產生一個 TodoModuleTodoController

$ nest generate module features/todo
$ nest generate controller features/todo

接著,在 features/todo 下新增 dto 資料夾,並建立 create-todo.dto.tshttps://ithelp.ithome.com.tw/upload/images/20210330/201193386bVroj82VD.png

在驗證格式機制上,必須要採用 class 的形式建立 DTO,原因在Controller(下)這篇有提過,如果採用 interface 的方式在編譯成 JavaScript 時會被刪除,如此一來,Nest 便無法得知 DTO 的格式為何。這裡我們先簡單定義一下 create-todo.dto.ts 的內容:

export class CreateTodoDto {
public readonly title: string;
public readonly description?: string;
}

我希望 title 的規則如下:

  1. 為必填
  2. 必須是 String
  3. 最大長度為 20

description 的規則如下:

  1. 為選填
  2. 必須是 String

那要如何套用這些規則呢?非常簡單,透過 class-validator 就能辦到,主要是替這些屬性添加特定的裝飾器:

import { IsNotEmpty, IsOptional, IsString, MaxLength } from "class-validator";

export class CreateTodoDto {
@MaxLength(20)
@IsString()
@IsNotEmpty()
public readonly title: string;

@IsString()
@IsOptional()
public readonly description?: string;
}

提醒:詳細的裝飾器內容可以參考 class-validator

如此一來便完成了規則的定義,實在是太好用啦!接下來只需要在資源上透過 @UsePipes裝飾器套用 ValidationPipe 即可:

import { Body, Controller, Post, UsePipes, ValidationPipe } from "@nestjs/common";
import { CreateTodoDto } from "./dto/create-todo.dto";

@Controller("todos")
export class TodoController {
@Post()
@UsePipes(ValidationPipe)
create(@Body() dto: CreateTodoDto) {
return {
id: 1,
...dto,
};
}
}

在 Controller 層級套用也可以,就會變成該 Controller 下的所有資源都支援驗證:

import { Body, Controller, Post, UsePipes, ValidationPipe } from "@nestjs/common";
import { CreateTodoDto } from "./dto/create-todo.dto";

@Controller("todos")
@UsePipes(ValidationPipe)
export class TodoController {
@Post()
create(@Body() dto: CreateTodoDto) {
return {
id: 1,
...dto,
};
}
}

透過 Postman 來測試,會發現順利報錯: https://ithelp.ithome.com.tw/upload/images/20210327/201193382apLrluOBd.png

關閉錯誤細項

如果不想要回傳錯誤的項目,可以透過 ValidationPipedisableErrorMessages 來關閉:

import { Body, Controller, Post, UsePipes, ValidationPipe } from "@nestjs/common";
import { CreateTodoDto } from "./dto/create-todo.dto";

@Controller("todos")
export class TodoController {
@Post()
@UsePipes(new ValidationPipe({ disableErrorMessages: true }))
create(@Body() dto: CreateTodoDto) {
return {
id: 1,
...dto,
};
}
}

透過 Postman 進行測試: https://ithelp.ithome.com.tw/upload/images/20210327/20119338aRUDKawPPJ.png

自訂 Exception

與其他 Pipe 一樣可以透過 exceptionFactory 自訂 Exception:

import { Body, Controller, HttpStatus, NotAcceptableException, Post, UsePipes, ValidationPipe } from "@nestjs/common";
import { ValidationError } from "class-validator";
import { CreateTodoDto } from "./dto/create-todo.dto";

@Controller("todos")
export class TodoController {
@Post()
@UsePipes(
new ValidationPipe({
exceptionFactory: (errors: ValidationError[]) => {
return new NotAcceptableException({
code: HttpStatus.NOT_ACCEPTABLE,
message: "格式錯誤",
errors,
});
},
})
)
create(@Body() dto: CreateTodoDto) {
return {
id: 1,
...dto,
};
}
}

透過 Postman 進行測試: https://ithelp.ithome.com.tw/upload/images/20210327/20119338Kgg9sLVbo4.png

自動過濾屬性

以前面新增 Todo 的例子來說,可接受的參數為 titledescription,假設今天客戶端傳送下方資訊:

{
"title": "Test",
"text": "Hello."
}

可以發現傳了一個毫無關聯的 text,這時候想要快速過濾掉這種無效參數該怎麼做呢?透過 ValidationPipe 設置 whitelist 即可,當 whitelisttrue 時,會 自動過濾掉於 DTO 沒有任何裝飾器的屬性,也就是說,就算有該屬性但沒有添加 class-validator 的裝飾器也會被視為無效屬性。這裡我們簡單實驗一下 whitelist

import { Body, Controller, Post, UsePipes, ValidationPipe } from "@nestjs/common";
import { CreateTodoDto } from "./dto/create-todo.dto";

@Controller("todos")
export class TodoController {
@Post()
@UsePipes(new ValidationPipe({ whitelist: true }))
create(@Body() dto: CreateTodoDto) {
return {
id: 1,
...dto,
};
}
}

透過 Postman 進行測試: https://ithelp.ithome.com.tw/upload/images/20210327/2011933846uvWl8O5Z.png

如果想要傳送無效參數時直接報錯的話,則是同時使用 whitelistforbidNonWhitelisted

import { Body, Controller, Post, UsePipes, ValidationPipe } from "@nestjs/common";
import { CreateTodoDto } from "./dto/create-todo.dto";

@Controller("todos")
export class TodoController {
@Post()
@UsePipes(new ValidationPipe({ whitelist: true, forbidNonWhitelisted: true }))
create(@Body() dto: CreateTodoDto) {
return {
id: 1,
...dto,
};
}
}

透過 Postman 進行測試: https://ithelp.ithome.com.tw/upload/images/20210327/201193388kPqruSHT6.png

自動轉換

ValidationPipe 還提供 transform 參數來轉換傳入的物件,將其實例化為對應的 DTO:

import { Body, Controller, Post, UsePipes, ValidationPipe } from "@nestjs/common";
import { CreateTodoDto } from "./dto/create-todo.dto";

@Controller("todos")
export class TodoController {
@Post()
@UsePipes(new ValidationPipe({ transform: true }))
create(@Body() dto: CreateTodoDto) {
console.log(dto);
return dto;
}
}

透過 Postman 進行測試,會在終端機看到下方結果,會發現 dtoCreateTodoDto 實例:

CreateTodoDto { title: 'Test' }

transform 還有一個很厲害的功能,還記得如何取得路由參數嗎?假設路由參數要取得 id,這個 id 型別是 number,但正常來說路由參數收到的時候都會是 string,透過 transform Nest 會嘗試去轉換成我們指定的型別:

import { Controller, Get, Param, UsePipes, ValidationPipe } from "@nestjs/common";

@Controller("todos")
export class TodoController {
@Get(":id")
@UsePipes(new ValidationPipe({ transform: true }))
get(@Param("id") id: number) {
console.log(typeof id);
return "";
}
}

透過瀏覽器存取 http://localhost:3000/1,會在終端機看到型別確實轉換成 number 了:

number;

檢測陣列 DTO

如果傳入的物件為陣列格式,不能使用 ValidationPipe,要使用 ParseArrayPipe,並在 items 帶入其 DTO:

import { Body, Controller, ParseArrayPipe, Post } from "@nestjs/common";
import { CreateTodoDto } from "./dto/create-todo.dto";

@Controller("todos")
export class TodoController {
@Post()
create(
@Body(new ParseArrayPipe({ items: CreateTodoDto }))
dtos: CreateTodoDto[]
) {
return dtos;
}
}

透過 Postman 進行測試: https://ithelp.ithome.com.tw/upload/images/20210330/20119338kOolhua1iv.png

解析查詢參數

ParseArrayPipe 還可以用來解析查詢參數,假設查詢參數為 ?ids=1,2,3,此時就可以善用此方法來解析出各個 id,只需要添加 separator 去判斷以什麼作為分界點:

import { Controller, Get, ParseArrayPipe, Query } from "@nestjs/common";

@Controller("todos")
export class TodoController {
@Get()
get(
@Query("ids", new ParseArrayPipe({ items: Number, separator: "," }))
ids: number[]
) {
return ids;
}
}

透過 Postman 進行測試: https://ithelp.ithome.com.tw/upload/images/20210330/201193381s3cN9Kr9d.png

DTO 技巧

當系統越來越龐大的時候,DTO 的數量也會隨之增加,有許多的 DTO 會有重複的屬性,例如:相同資源下的 CRUD DTO,這時候就會變得較難維護,還好 Nest 有提供良好的解決方案,運用特殊的繼承方式來處理:

局部性套用 (Partial)

局部性套用的意思是將既有的 DTO 所有欄位都取用,只是全部轉換為非必要屬性,需要使用到 PartialType 這個函式來把要取用的 DTO 帶進去,並給新的 DTO 繼承。這邊我們先建立一個 update-todo.dto.tsdto 資料夾中,並讓它繼承 CreateTodoDto 的欄位:

import { PartialType } from "@nestjs/mapped-types";
import { CreateTodoDto } from "./create-todo.dto";

export class UpdateTodoDto extends PartialType(CreateTodoDto) {}

其效果相當於:

import { IsNotEmpty, IsOptional, IsString, MaxLength } from "class-validator";

export class UpdateTodoDto {
@MaxLength(20)
@IsString()
@IsNotEmpty()
@IsOptional()
public readonly title?: string;

@IsString()
@IsOptional()
public readonly description?: string;
}

接著來修改 todo.controller.ts

import { Body, Controller, Param, Patch, UsePipes, ValidationPipe } from "@nestjs/common";
import { UpdateTodoDto } from "./dto/update-todo.dto";

@Controller("todos")
export class TodoController {
@Patch(":id")
@UsePipes(ValidationPipe)
update(@Param("id") id: number, @Body() dto: UpdateTodoDto) {
return {
id,
...dto,
};
}
}

透過 Postman 進行測試,這邊我不帶任何值去存取 PATCH /todos/:id,會發現可以通過驗證: https://ithelp.ithome.com.tw/upload/images/20210331/20119338VFWblhWwgx.png

選擇性套用 (Pick)

選擇性套用的意思是用既有的 DTO 去選擇哪些是會用到的屬性,需要使用到 PickType 這個函式來把要取用的 DTO 帶進去以及指定要用的屬性名稱,並給新的 DTO 繼承。這邊我們沿用 UpdateTodoDto 並讓它繼承 CreateTodoDtotitle 欄位:

import { PickType } from "@nestjs/mapped-types";
import { CreateTodoDto } from "./create-todo.dto";

export class UpdateTodoDto extends PickType(CreateTodoDto, ["title"]) {}

其效果等同於:

import { IsNotEmpty, IsString, MaxLength } from "class-validator";

export class UpdateTodoDto {
@MaxLength(20)
@IsString()
@IsNotEmpty()
public readonly title: string;
}

todo.controller.ts 沿用前面的範例:

import { Body, Controller, Param, Patch, UsePipes, ValidationPipe } from "@nestjs/common";
import { UpdateTodoDto } from "./dto/update-todo.dto";

@Controller("todos")
export class TodoController {
@Patch(":id")
@UsePipes(ValidationPipe)
update(@Param("id") id: number, @Body() dto: UpdateTodoDto) {
return {
id,
...dto,
};
}
}

透過 Postman 進行測試,這邊我不帶任何值去存取 PATCH /todos/:id,會發現無法通過驗證: https://ithelp.ithome.com.tw/upload/images/20210331/20119338LHx2pVlIIr.png

忽略套用 (Omit)

忽略套用的意思是用既有的 DTO 但忽略不會用到的屬性,需要使用到 OmitType 這個函式來把要取用的 DTO 帶進去以及指定要忽略的屬性名稱,並給新的 DTO 繼承。這邊我們沿用 UpdateTodoDto 並讓它繼承 CreateTodoDto 的欄位,但忽略 title 屬性:

import { OmitType } from "@nestjs/mapped-types";
import { CreateTodoDto } from "./create-todo.dto";

export class UpdateTodoDto extends OmitType(CreateTodoDto, ["title"]) {}

其效果等同於:

import { IsOptional, IsString } from "class-validator";

export class UpdateTodoDto {
@IsString()
@IsOptional()
public readonly description?: string;
}

這裡稍微調整一下 todo.controller.ts,將 whitelistforbidNonWhitelisted 設為 true

import { Body, Controller, Param, Patch, UsePipes, ValidationPipe } from "@nestjs/common";
import { UpdateTodoDto } from "./dto/update-todo.dto";

@Controller("todos")
export class TodoController {
@Patch(":id")
@UsePipes(new ValidationPipe({ whitelist: true, forbidNonWhitelisted: true }))
update(@Param("id") id: number, @Body() dto: UpdateTodoDto) {
return {
id,
...dto,
};
}
}

透過 Postman 進行測試,這邊我刻意帶 title 去存取 PATCH /todos/:id,由於設置了 whitelistforbidNonWhitelisted,所以無法通過驗證: https://ithelp.ithome.com.tw/upload/images/20210331/20119338Zl2bUEEZbE.png

合併套用 (Intersection)

合併套用的意思是用既有的兩個 DTO 來合併屬性,需要使用到 IntersectionType 這個函式來把要取用的兩個 DTO 帶進去,並給新的 DTO 繼承。這邊我們沿用 CreateTodoDto 並在 update-todo.dto.ts 新增一個 MockDto,再讓 UpdateTodoDto 去繼承這兩個的欄位:

import { IntersectionType } from "@nestjs/mapped-types";
import { IsNotEmpty, IsString } from "class-validator";
import { CreateTodoDto } from "./create-todo.dto";

export class MockDto {
@IsString()
@IsNotEmpty()
public readonly information: string;
}

export class UpdateTodoDto extends IntersectionType(CreateTodoDto, MockDto) {}

其效果等同於:

import { IsNotEmpty, IsOptional, IsString, MaxLength } from "class-validator";

export class UpdateTodoDto {
@MaxLength(20)
@IsString()
@IsNotEmpty()
public readonly title: string;

@IsString()
@IsOptional()
public readonly description?: string;

@IsString()
@IsNotEmpty()
public readonly information: string;
}

這裡調整一下 todo.controller.ts

import { Body, Controller, Param, Patch, UsePipes, ValidationPipe } from "@nestjs/common";
import { UpdateTodoDto } from "./dto/update-todo.dto";

@Controller("todos")
export class TodoController {
@Patch(":id")
@UsePipes(ValidationPipe)
update(@Param("id") id: number, @Body() dto: UpdateTodoDto) {
return {
id,
...dto,
};
}
}

透過 Postman 進行測試,這邊我刻意不帶 information 去存取 PATCH /todos/:id,所以無法通過驗證: https://ithelp.ithome.com.tw/upload/images/20210331/20119338CSib3iRbem.png

組合應用

上述的四個函式:PartialTypePickTypeOmitTypeIntersectionType 是可以透過組合的方式來使用的。下方的範例使用 OmitTypeCreateTodoDtotitle 欄位去除,並使用 IntersectionTypeMockDto 與之合併 :

import { IntersectionType, OmitType } from "@nestjs/mapped-types";
import { IsNotEmpty, IsString } from "class-validator";
import { CreateTodoDto } from "./create-todo.dto";

export class MockDto {
@IsString()
@IsNotEmpty()
public readonly information: string;
}

export class UpdateTodoDto extends IntersectionType(OmitType(CreateTodoDto, ["title"]), MockDto) {}

其效果等同於:

import { IsNotEmpty, IsOptional, IsString } from "class-validator";

export class UpdateTodoDto {
@IsString()
@IsOptional()
public readonly description?: string;

@IsString()
@IsNotEmpty()
public readonly information: string;
}

todo.controller.ts 保持本來的樣子:

import { Body, Controller, Param, Patch, UsePipes, ValidationPipe } from "@nestjs/common";
import { UpdateTodoDto } from "./dto/update-todo.dto";

@Controller("todos")
export class TodoController {
@Patch(":id")
@UsePipes(ValidationPipe)
update(@Param("id") id: number, @Body() dto: UpdateTodoDto) {
return {
id,
...dto,
};
}
}

透過 Postman 進行測試,這邊我不帶任何值去存取 PATCH /todos/:id,會發現無法通過驗證: https://ithelp.ithome.com.tw/upload/images/20210401/201193382aLm99F5LW.png

全域 Pipe

ValidationPipe 算是一個蠻常用的功能,因為大多數的情況都會使用到 DTO 的概念,如此一來便可以使用 DTO 驗證的方式去檢查資料的正確性,所以可以直接將 ValidationPipe 配置在全域,僅需要修改 main.ts 即可:

import { ValidationPipe } from "@nestjs/common";
import { NestFactory } from "@nestjs/core";
import { AppModule } from "./app.module";

async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(new ValidationPipe());
await app.listen(3000);
}
bootstrap();

透過 useGlobalPipes 使 ValidationPipe 適用於全域,實在是非常方便!

依賴注入實作全域 Pipe

上面的方法是透過模組外部完成全域配置的,與 Exception filter 一樣可以用依賴注入的方式,透過指定 Provider 的 tokenAPP_PIPE 來實現,這裡是用 useClass 來指定要建立實例的類別:

import { Module, ValidationPipe } from "@nestjs/common";
import { APP_PIPE } from "@nestjs/core";
import { AppController } from "./app.controller";
import { AppService } from "./app.service";
import { TodoModule } from "./features/todo/todo.module";

@Module({
imports: [TodoModule],
controllers: [AppController],
providers: [
AppService,
{
provide: APP_PIPE,
useClass: ValidationPipe,
},
],
})
export class AppModule {}

小結

ValidationPipe 與 DTO 的驗證機制十分好用且重要,任何的 API 都需要做好完善的資料檢查,才能夠降低帶來的風險。這裡附上今天的懶人包:

  1. ValidationPipe 需要安裝 class-validatorclass-transformer
  2. 透過 ValidationPipe 可以實現 DTO 格式驗證。
  3. ValidationPipe 可以透過 disableErrorMessages 關閉錯誤細項。
  4. ValidationPipe 一樣可以透過 exceptionFactory 自訂 Exception。
  5. ValidationPipe 可以透過 whitelist 來過濾無效參數,如果接收到無效參數想要回傳錯誤的話,還需要額外啟用 forbidNonWhitelisted
  6. ValidationPipe 可以透過 transform 來達到自動轉換型別的效果。
  7. ParseArrayPipe 解析陣列 DTO 以及查詢參數。
  8. DTO 可以透過 PartialTypePickTypeOmitTypeIntersectionType 這四個函式來重用 DTO 的欄位。
  9. 全域 Pipe 可以透過依賴注入的方式實作。