NestJS 驗證

2023-09-08 17:44 更新

驗證網(wǎng)絡(luò)應(yīng)用中傳遞的任何數(shù)據(jù)是一種最佳實踐。為了自動驗證傳入請求, Nest 提供了幾個開箱即用的管道。

  • ValidationPipe
  • ParseIntPipe
  • ParseBoolPipe
  • ParseArrayPipe
  • ParseUUIDPipe

ValidationPipe 使用了功能強大的 class-validator 包及其聲明性驗證裝飾器。 ValidationPipe 提供了一種對所有傳入的客戶端有效負載強制執(zhí)行驗證規(guī)則的便捷方法,其中在每個模塊的本地類或者 DTO 聲明中使用簡單的注釋聲明特定的規(guī)則。

概覽

在 Pipes 一章中,我們完成了構(gòu)建簡化驗證管道的過程。為了更好地了解我們在幕后所做的工作,我們強烈建議您閱讀本文。在這里,我們將重點討論 ValidationPipe 的各種實際用例,并使用它的一些高級定制特性。

使用內(nèi)置的ValidationPipe

在開始使用之前,我們先安裝依賴。

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

ValidationPipe 從 @nestjs/common 包導(dǎo)入。

由于此管道使用了 class-validator 和 class-transformer 庫,因此有許多可用的選項。通過傳遞給管道的配置對象來進行配置。依照下列內(nèi)置的選項:

export interface ValidationPipeOptions extends ValidatorOptions {
  transform?: boolean;
  disableErrorMessages?: boolean;
  exceptionFactory?: (errors: ValidationError[]) => any;
}

所有可用的class-validator選項(繼承自ValidatorOptions接口):

選項類型描述
enableDebugMessagesboolean如果設(shè)置為 true ,驗證器會在出問題的時候打印額外的警告信息
skipUndefinedPropertiesboolean如果設(shè)置為 true ,驗證器將跳過對所有驗證對象中值為 null 的屬性的驗證
skipNullPropertiesboolean如果設(shè)置為 true ,驗證器將跳過對所有驗證對象中值為 null 或 undefined 的屬性的驗證
skipMissingPropertiesboolean如果設(shè)置為 true ,驗證器將跳過對所有驗證對象中缺失的屬性的驗證
whitelistboolean如果設(shè)置為 true ,驗證器將去掉沒有使用任何驗證裝飾器的屬性的驗證(返回的)對象
forbidNonWhitelistedboolean如果設(shè)置為 true ,驗證器不會去掉非白名單的屬性,而是會拋出異常
forbidUnknownValuesboolean如果設(shè)置為 true ,嘗試驗證未知對象會立即失敗
disableErrorMessageboolean如果設(shè)置為 true ,驗證錯誤不會返回給客戶端
errorHttpStatusCodenumber這個設(shè)置允許你確定在錯誤時使用哪個異常類型。默認拋出 BadRequestException
exceptionFactoryFunction接受一個驗證錯誤數(shù)組并返回一個要拋出的異常對象
groupsstring[]驗證對象時使用的分組
alwaysboolean設(shè)置裝飾器選項 always 的默認值。默認值可以在裝飾器的選項中被覆寫
strictGroupsboolean忽略在任何分組內(nèi)的裝飾器,如果 groups 沒有給出或者為空
dismissDefaultMessagesboolean如果設(shè)置為 true ,將不會使用默認消息驗證,如果不設(shè)置,錯誤消息會始終是 undefined
validationError.targetboolean確定目標是否要在 ValidationError 中暴露出來
validationError.valueboolean確定驗證值是否要在 ValidationError 中暴露出來
stopAtFirstErrorboolean如果設(shè)置為 true ,對于給定的屬性的驗證會在觸發(fā)第一個錯誤之后停止。默認為 false

更多關(guān)于class-validator包的內(nèi)容見項目倉庫。

自動驗證

為了本教程的目的,我們將綁定 ValidationPipe 到整個應(yīng)用程序,因此,將自動保護所有接口免受不正確的數(shù)據(jù)的影響。

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

要測試我們的管道,讓我們創(chuàng)建一個基本接口。

@Post()
create(@Body() createUserDto: CreateUserDto) {
  return 'This action adds a new user';
}

由于 Typescript 沒有保存 泛型或接口 的元數(shù)據(jù)。當你在你的 DTO 中使用他們的時候。 ValidationPipe 可能不能正確驗證輸入數(shù)據(jù)。出于這種原因,可以考慮在你的 DTO 中使用具體的類。

當你導(dǎo)入你的 DTO 時,你不能使用僅類型的導(dǎo)入,因為類型會在運行時被擦除,記得用 import { CreateUserDto } 而不是 import type { CreateUserDto } 。

現(xiàn)在我們可以在 CreateUserDto 中添加一些驗證規(guī)則。我們使用 class-validator 包提供的裝飾器來實現(xiàn)這一點,這里有詳細的描述。以這種方式,任何使用 CreateUserDto 的路由都將自動執(zhí)行這些驗證規(guī)則。

import { IsEmail, IsNotEmpty } from 'class-validator';

export class CreateUserDto {
  @IsEmail()
  email: string;

  @IsNotEmpty()
  password: string;
}

有了這些規(guī)則,當某人使用無效 email 執(zhí)行對我們的接口的請求時,則應(yīng)用程序?qū)⒆詣右?nbsp;400 Bad Request 代碼以及以下響應(yīng)正文進行響應(yīng):

{
  "statusCode": 400,
  "error": "Bad Request",
  "message": ["email must be an email"]
}

除了驗證請求主體之外,ValidationPipe 還可以與其他請求對象屬性一起使用。假設(shè)我們希望接受端點路徑中的 id 。為了確保此請求參數(shù)只接受數(shù)字,我們可以使用以下結(jié)構(gòu):

@Get(':id')
findOne(@Param() params: FindOneParams) {
  return 'This action returns a user';
}

與 DTO 一樣,F(xiàn)indOneParams 只是一個使用 class-validator 定義驗證規(guī)則的類。它是這樣的:

import { IsNumberString } from 'class-validator';

export class FindOneParams {
  @IsNumberString()
  id: number;
}

禁用詳細錯誤

錯誤消息有助于解釋請求中的錯誤。然而,一些生產(chǎn)環(huán)境傾向于禁用詳細的錯誤。通過向 ValidationPipe 傳遞一個選項對象來做到這一點:

app.useGlobalPipes(
  new ValidationPipe({
    disableErrorMessages: true,
  })
);

現(xiàn)在,不會將錯誤消息返回給最終用戶。

剝離屬性

我們的 ValidationPipe 還可以過濾掉方法處理程序不應(yīng)該接收的屬性。在這種情況下,我們可以對可接受的屬性進行白名單,白名單中不包含的任何屬性都會自動從結(jié)果對象中刪除。例如,如果我們的處理程序需要 email 和 password,但是一個請求還包含一個 age 屬性,那么這個屬性可以從結(jié)果 DTO 中自動刪除。要啟用這種行為,請將 whitelist 設(shè)置為 true 。

app.useGlobalPipes(
  new ValidationPipe({
    whitelist: true,
  })
);

當設(shè)置為 true 時,這將自動刪除非白名單屬性(在驗證類中沒有任何修飾符的屬性)。

或者,您可以在出現(xiàn)非白名單屬性時停止處理請求,并向用戶返回錯誤響應(yīng)。要啟用此選項,請將 forbidNonWhitelisted 選項屬性設(shè)置為 true ,并將 whitelist 設(shè)置為 true。

負載對象轉(zhuǎn)換(Transform)

來自網(wǎng)絡(luò)的有效負載是普通的 JavaScript 對象。ValidationPipe 可以根據(jù)對象的 DTO 類自動將有效負載轉(zhuǎn)換為對象類型。若要啟用自動轉(zhuǎn)換,請將 transform 設(shè)置為 true。這可以在方法級別使用:

cats.control.ts
@Post()
@UsePipes(new ValidationPipe({ transform: true }))
async create(@Body() createCatDto: CreateCatDto) {
  this.catsService.create(createCatDto);
}

要在全局啟用這一行為,將選項設(shè)置到一個全局管道中:

app.useGlobalPipes(
  new ValidationPipe({
    transform: true,
  })
);

要使能自動轉(zhuǎn)換選項,ValidationPipe將執(zhí)行簡單類型轉(zhuǎn)換。在下述示例中,findOne()方法調(diào)用一個從地址參數(shù)中解析出的id參數(shù)。

@Get(':id')
findOne(@Param('id') id: number) {
  console.log(typeof id === 'number'); // true
  return 'This action returns a user';
}

默認地,每個地址參數(shù)和查詢參數(shù)在網(wǎng)絡(luò)傳輸時都是 string 類型。在上述示例中,我們指定 id 參數(shù)為 number (在方法簽名中)。因此,ValidationPipe會自動將 string 類型轉(zhuǎn)換為 number 。

顯式轉(zhuǎn)換

在上述部分,我們演示了 ValidationPipe 如何基于期待類型隱式轉(zhuǎn)換查詢和路徑參數(shù),然而,這一特性需要開啟自動轉(zhuǎn)換功能。

可選地(在不開啟自動轉(zhuǎn)換功能的情況下),你可以使用 ParseIntPipe 或者 ParseBoolPipe 顯式處理值(注意,沒有必要使用 ParseStringPipe ,這是因為如前所述的,網(wǎng)絡(luò)中傳輸?shù)穆窂絽?shù)和查詢參數(shù)默認都是 string 類型)。

@Get(':id')
findOne(
  @Param('id', ParseIntPipe) id: number,
  @Query('sort', ParseBoolPipe) sort: boolean,
) {
  console.log(typeof id === 'number'); // true
  console.log(typeof sort === 'boolean'); // true
  return 'This action returns a user';
}

ParseIntPipe和ParseBoolPipe從@nestjs/common包中導(dǎo)出。

映射類型

當你在編寫如增刪改查(新增/刪除/修改/查詢)的新功能的時候,你會經(jīng)常基于一個實體類型來構(gòu)造一個變種。 Nest 提供了一些可以進行類型轉(zhuǎn)換的功能函數(shù)來讓這種任務(wù)更加方便。

如果你的應(yīng)用使用了 @nestjs/swagger 包,請看這一章節(jié)來了解更多有關(guān)映射類型的信息。類似地,如果你使用了 @nestjs/graphql 包請看這一章節(jié)。這幾個包都十分依賴類型所以需要分開導(dǎo)入以使用。因此,如果你使用了 @nestjs/mapped-types (而不是合適的包,根據(jù)你應(yīng)用的類型是 @nestjs/swagger 或者 @nestjs/graphql ),你可能會碰到各種各樣的沒有被文檔記錄的副作用。

當構(gòu)造輸入驗證類型(也稱為 DTO )時,你往往會在同一個類型上構(gòu)造 創(chuàng)建 和 更新 變種。舉個例子, 創(chuàng)建 變種可能要求全部的字段都被填寫,但是 更新 變種可能會把全部的字段變成可選的。

Nest 提供了 PartialType() 函數(shù)來讓這個任務(wù)變得簡單,同時也可以減少樣板代碼。

PartialType() 函數(shù)返回一個類型(一個類)包含被設(shè)置成可選的所有輸入類型的屬性。假設(shè)我們有一個 創(chuàng)建 的類型:

export class CreateCatDto {
  name: string;
  age: number;
  breed: string;
}

在默認情況下,所有的字段都是被需要的。使用 PartialType() 并把類引用( CreateCatDto )當作參數(shù)傳入就可以創(chuàng)造一個有著相同字段但是每一個字段都是可選的新類型:

export class UpdateCatDto extends PartialType(CreateCatDto) {}

PartialType() 函數(shù)是從 @nestjs/mapped-types 包導(dǎo)入的。

PickType() 函數(shù)通過挑出輸入類型的一組屬性構(gòu)造一個新的類型(類)。假設(shè)我們有以下的類型:

export class CreateCatDto {
  name: string;
  age: number;
  breed: string;
}

我們可以使用 PickType() 函數(shù)從這個類中挑出一組屬性:

export class UpdateCatAgeDto extends PickType(CreateCatDto, ['age'] as const) {}

PickType() 函數(shù)是從 @nestjs/mapped-types 包導(dǎo)入的。

OmitType() 函數(shù)通過挑出輸入類型中的全部屬性,然后移除一組特定的屬性構(gòu)造一個類型。假設(shè)我們有以下的類型:

export class CreateCatDto {
  name: string;
  age: number;
  breed: string;
}

如下所示,我們可以生成一個派生的擁有除了 name 以外的所有屬性的類型。在這個結(jié)構(gòu)中,給 OmitType() 的第二個參數(shù)是一個包含了屬性名的數(shù)組:

export class UpdateCatDto extends OmitType(CreateCatDto, ['name'] as const) {}

OmitType() 函數(shù)是從 @nestjs/mapped-types 包導(dǎo)入的。

IntersectionType() 函數(shù)將兩個類型合并成一個類型。假設(shè)我們有以下的兩個類型:

export class CreateCatDto {
  name: string;
  breed: string;
}

export class AdditionalCatInfo {
  color: string;
}

我們可以生成一個合并了兩個類型中所有屬性的新類型:

export class UpdateCatDto extends IntersectionType(
  CreateCatDto,
  AdditionalCatInfo,
) {}

IntersectionType() 函數(shù)是從 @nestjs/mapped-types 包導(dǎo)入的。

這些映射類型函數(shù)是可以組合的。下面的例子會創(chuàng)造一個擁有除了 name 屬性以外所有的 CreateCatDto 的屬性,而且這些屬性是可選的:

export class UpdateCatDto extends PartialType(
  OmitType(CreateCatDto, ['name'] as const),
) {}

轉(zhuǎn)換和驗證數(shù)組

TypeScript 不存儲泛型或接口的元數(shù)據(jù),因此當你在 DTO 中使用它們的時候, ValidationPipe 可能不能正確驗證輸入數(shù)據(jù)。例如,在下列代碼中, createUserDto 不能正確驗證。

@Post()
createBulk(@Body() createUserDtos: CreateUserDto[]) {
  return 'This action adds new users';
}

要驗證數(shù)組,創(chuàng)建一個包裹了該數(shù)組的專用類,或者使用 ParseArrayPipe 。

@Post()
createBulk(
  @Body(new ParseArrayPipe({ items: CreateUserDto }))
  createUserDtos: CreateUserDto[],
) {
  return 'This action adds new users';
}

此外, ParseArrayPipe 可能需要手動解析查詢參數(shù)。讓我們考慮一個返回作為查詢參數(shù)傳遞的標識的 users 的 findByIds() 方法:

@Get()
findByIds(
  @Query('id', new ParseArrayPipe({ items: Number, separator: ',' }))
  ids: number[],
) {
  return 'This action returns users by ids';
}

這個構(gòu)造用于驗證一個來自如下形式帶參數(shù)的 GET 請求:

GET /?ids=1,2,3

Websockets 和 微服務(wù)

盡管本章展示了使用 HTTP 風(fēng)格的應(yīng)用程序的例子(例如,Express或 Fastify ), ValidationPipe 對于 WebSockets 和微服務(wù)是一樣的,不管使用什么傳輸方法。

學(xué)到更多

要閱讀有關(guān)由 class-validator 提供的自定義驗證器,錯誤消息和可用裝飾器的更多信息,請訪問此頁面。


以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號