iT邦幫忙

2023 iThome 鐵人賽

0
自我挑戰組

用ChatGPT詠唱來完成工作與點亮前後端技能樹系列 第 44

【Day44】ChatGPT請教教我:NestJS!(三)- Controller & 資料驗證 !class-validator & class-transformer!

  • 分享至 

  • xImage
  •  

上一篇提到Controller 還有一些可以設置資料驗證、或是設置中間件等
這一篇預計詳細探討有關資料驗證的部分

  1. DTO
  2. class-validator
  3. class-transformer

然後下一篇才會探討中間件相關:

  1. Middleware
  2. Pipes
  3. Guards
  4. Interceptors
  5. Filters

沒問題我們就走起!

DTO & class-validator

https://ithelp.ithome.com.tw/upload/images/20231028/20135750u2udoHr3ou.png

在 NestJS 中,DTO(Data Transfer Object)是一個非常重要的概念,它在開發過程中扮演著至關重要的角色。讓我用繁體中文來詳細介紹一下:

DTO 的作用是幫助我們在不同層之間傳遞數據,同時也可以幫助我們控制數據的流動。以下是 DTO 的主要作用:

  1. 數據驗證和過濾
    DTO 允許我們在控制器層(Controller)接收請求數據之前進行驗證和過濾。這樣可以確保我們處理的數據是有效且符合預期的。
  2. 解耦數據模型
    DTO 允許我們將外部請求的數據結構與內部數據模型進行區分。這樣即使外部數據的結構發生變化,內部處理的邏輯也可以保持不變。
  3. 隱藏敏感信息
    DTO 可以用於過濾掉請求中不必要的數據,以保護敏感信息不被外部訪問到。
  4. 增強代碼的可讀性和可維護性
    通過使用 DTO,我們可以清晰地看到接口的輸入和輸出,這使得代碼更易於理解、測試和維護。
  5. 幫助文檔撰寫
    DTO 可以作為 API 接口的文檔一部分,使得開發者和團隊成員能夠清楚了解接口的輸入和輸出。

舉例來說,假設你正在開發一個博客應用程式,當你接收到一個新增文章的請求時,你可以使用一個名為 CreatePostDto 的 DTO 來驗證請求中的數據是否符合預期,並將其傳遞給服務層進行處理。

// 在 NestJS 中,一個簡單的 DTO 可以長這樣:
export class CreatePostDto {
  readonly title: string;
  readonly content: string;
  readonly authorId: number;
}

總的來說,DTO 是 NestJS 中一個非常重要的概念,它有助於保持代碼的清晰性、可讀性和可維護性,同時也提升了開發過程中的效率。希望這能對你有所幫助!如果你有任何問題,請隨時提出。

上面只提到「可以驗證和過濾」,但實際上只有像上面設置了DTO並指定query Type
就算回傳的不是指定的type,也不會噴出任何錯誤
要一直到執行階段才會噴錯

比如說這樣,把 app.controller.ts 改成這樣

import { Controller, Get, Query } from '@nestjs/common';
import { AppService } from './app.service';
export class TestRequestDTO {
  name: string;
}

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

  @Get('test')
  async getTest(@Query() query: TestRequestDTO): Promise<string> {
    return `get hello!! ${query.name}`;
  }
}

然後訪問 http://localhost:3000/test
會發現一樣會執行,而且沒有任何錯誤訊息
https://ithelp.ithome.com.tw/upload/images/20231028/20135750mrdrHuNErW.png

所以要問問ChatGPT所謂的驗證跟過濾是怎麼一回事
https://ithelp.ithome.com.tw/upload/images/20231028/201357505C5TgzGOnl.png

當你在 NestJS 中使用 DTO 進行驗證和過濾時,你通常會使用一個叫做 class-validator 的庫,它可以與 NestJS 配合得很好。讓我為你提供一個完整的教學:

步驟一:安裝相關套件

首先,你需要安裝 class-validatorclass-transformer 這兩個套件。你可以在你的 NestJS 專案中使用以下指令進行安裝:

npm install class-validator class-transformer

步驟二:創建 DTO

假設你想要創建一個用於驗證用戶註冊的 DTO,我們可以創建一個名為 RegisterUserDto 的類別:

// register-user.dto.ts
import { IsString, IsEmail, IsNotEmpty, MinLength } from 'class-validator';

export class RegisterUserDto {
  @IsString()
  @IsNotEmpty()
  username: string;

  @IsEmail()
  email: string;

  @IsString()
  @MinLength(6)
  password: string;
}

在這個範例中,我們使用了 class-validator 提供的裝飾器來定義屬性的驗證規則。

  • @IsString() 確保了 username 是一個字串。
  • @IsNotEmpty() 確保了 username 不為空。
  • @IsEmail() 確保了 email 是一個有效的電子郵件地址。
  • @MinLength(6) 確保了 password 至少有 6 個字符。

步驟三:在控制器中使用 DTO

接著,在你的控制器中使用這個 DTO。例如,當你處理用戶註冊請求時:

// auth.controller.ts
import { Controller, Post, Body } from '@nestjs/common';
import { RegisterUserDto } from './register-user.dto';

@Controller('auth')
export class AuthController {
  @Post('register')
  register(@Body() registerUserDto: RegisterUserDto) {
    // 在這裡你可以安心地使用 registerUserDto,因為它已經通過了驗證
    // 進行用戶註冊的相關邏輯
  }
}

步驟四:全局註冊驗證器

為了使驗證生效,你還需要在 NestJS 的主模組中進行全局註冊。打開你的主模組(通常是 app.module.ts):

import { Module, ValidationPipe } from '@nestjs/common';
import { APP_PIPE } from '@nestjs/core';

@Module({
  providers: [
    {
      provide: APP_PIPE,
      useClass: ValidationPipe,
    },
    // 其他提供者...
  ],
  // ...
})
export class AppModule {}

這樣,所有進入你的應用程序的請求都將使用 ValidationPipe 進行驗證。

現在,當你發送一個用戶註冊的請求時,NestJS 將自動根據 RegisterUserDto 中定義的驗證規則進行驗證。

希望這個教學對你有所幫助!如果你有任何進一步的問題,請隨時提出。

https://ithelp.ithome.com.tw/upload/images/20231028/20135750e7xDgznVEK.png

如果傳入的資訊未通過驗證,NestJS 會自動回傳一個包含錯誤訊息的響應。這個響應的 HTTP 狀態碼通常是 400(Bad Request)。

錯誤訊息會包含了哪些屬性沒有通過驗證以及相對應的錯誤信息。這樣的回傳可以幫助客戶端了解為何請求被拒絕。

例如,假設我們使用前面提到的 RegisterUserDto 進行用戶註冊驗證,如果傳入的請求不符合規定(比如 password 長度小於 6 個字符),響應可能會如下所示:

{
  "statusCode": 400,
  "message": [
    "password must be longer than or equal to 6 characters"
  ],
  "error": "Bad Request"
}

這樣的回傳讓開發者和客戶端能夠清晰地知道哪些部分的請求未通過驗證,並提供了相應的錯誤訊息。

請注意,你可以在全局的錯誤過濾器中自定義這樣的響應格式,以符合你的需求。

希望這能幫到你!如果你有任何進一步的問題,請隨時提出。

相當方便是吧!當傳入的資料不符合規格,自動就會回傳錯誤
不用在進入function的時候個別去手寫驗證,也不用擔心型別錯誤,不用為了輸入的type苦惱

在串接測試的時候,更能第一時間明白與預防傳入的參數錯誤或打錯字

class-validator

沒問題我們就來學習完整的class-validator內容
https://ithelp.ithome.com.tw/upload/images/20231028/20135750A8dauZtHjN.png

class-validator 是一個用於在 TypeScript 或 JavaScript 中執行驗證的庫,它可以用於檢查對象是否符合特定的標準。在 NestJS 中,它通常與 DTO(Data Transfer Object)一起使用,用於驗證從客戶端發送的數據。

以下是 class-validator 的詳細介紹:

1. 裝飾器(Decorators)

class-validator 提供了一系列的裝飾器,它們用於在類別的屬性上定義驗證規則。這些裝飾器包括:

  • @IsString():確保屬性是字串。
  • @IsNotEmpty():確保屬性不為空。
  • @IsEmail():確保屬性是一個有效的電子郵件地址。
  • @MinLength():確保屬性的長度不小於指定值,可以用於字串、陣列等。
  • @MaxLength():確保屬性的長度不大於指定值。
  • 等等。

這些裝飾器允許你在 DTO 或其他類別的屬性上定義各種驗證規則,以確保數據的合法性。

2. 驗證器的使用

在 NestJS 中,你可以將這些裝飾器應用在 DTO 的屬性上,例如:

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

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

  @IsEmail()
  email: string;
}

在這個例子中,username 屬性必須是非空字串,而 email 屬性必須是有效的電子郵件地址。

3. 驗證器的組合

你可以使用多個驗證器組合在一起,以實現更複雜的驗證邏輯。例如:

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

export class UpdateUserDto {
  @IsString()
  @IsNotEmpty()
  @MinLength(6)
  @MaxLength(20)
  password: string;
}

在這個例子中,password 屬性必須是非空字串,並且長度必須在 6 到 20 個字符之間。

4. 全局驗證器

你也可以在全局範圍內應用驗證器,這樣所有進入你的應用程序的請求都將經過驗證。這在 NestJS 的主模組中實現,如前一則回答所示。

5. 自訂驗證器

除了內建的驗證器,你還可以定義自己的自訂驗證器,以滿足特定的需求。

這些是 class-validator 的一些基本概念,它提供了一個方便而靈活的方法來驗證數據,確保應用程序接收到符合預期的請求。希望這能幫到你!如果你有任何進一步的問題,請隨時提出。

內建驗證器

來看一下內建的驗證有哪些可以用!
https://ithelp.ithome.com.tw/upload/images/20231028/20135750Mn6EVqxTAP.png

當使用 class-validator 時,以下是一些常見的驗證器,以及它們的介紹和範例:

  1. @IsString():確保屬性是字串。
import { IsString } from 'class-validator';

class ExampleDto {
  @IsString()
  name: string;
}
  1. @IsNotEmpty():確保屬性不為空。
import { IsNotEmpty } from 'class-validator';

class ExampleDto {
  @IsNotEmpty()
  title: string;
}
  1. @IsInt():確保屬性是整數。
import { IsInt } from 'class-validator';

class ExampleDto {
  @IsInt()
  age: number;
}
  1. @IsEmail():確保屬性是有效的電子郵件地址。
import { IsEmail } from 'class-validator';

class ExampleDto {
  @IsEmail()
  email: string;
}
  1. @MinLength():確保字串或數組的長度不小於指定值。
import { MinLength } from 'class-validator';

class ExampleDto {
  @MinLength(6)
  password: string;
}
  1. @MaxLength():確保字串或數組的長度不大於指定值。
import { MaxLength } from 'class-validator';

class ExampleDto {
  @MaxLength(100)
  description: string;
}
  1. @IsEnum():確保屬性的值是列舉類別中的一個有效值。
import { IsEnum } from 'class-validator';

enum Gender {
  MALE = 'male',
  FEMALE = 'female',
  OTHER = 'other',
}

class ExampleDto {
  @IsEnum(Gender)
  gender: Gender;
}
  1. @IsNumber():確保屬性是數字。
import { IsNumber } from 'class-validator';

class ExampleDto {
  @IsNumber()
  price: number;
}

這些都是常見的驗證器,可以用於 DTO 或其他類別的屬性上,以確保數據的合法性。你可以組合這些驗證器以滿足特定的驗證需求。希望這些範例能幫助你更好地理解如何使用 class-validator。如果你有更多問題,請隨時提出。

每次都忍不住貪心一下
https://ithelp.ithome.com.tw/upload/images/20231028/20135750KeNTMbJwtI.png

除了之前提到的常見驗證器,class-validator 還提供了一些其他常用的驗證器,以下是一些示例:

  1. @IsNumberString():確保屬性的值是一個表示數字的字串。
import { IsNumberString } from 'class-validator';

class ExampleDto {
  @IsNumberString()
  phoneNumber: string;
}
  1. @IsDate():確保屬性的值是一個有效的日期。
import { IsDate } from 'class-validator';

class ExampleDto {
  @IsDate()
  birthDate: Date;
}
  1. @IsBoolean():確保屬性的值是一個布林值。
import { IsBoolean } from 'class-validator';

class ExampleDto {
  @IsBoolean()
  isActive: boolean;
}
  1. @IsArray():確保屬性的值是一個陣列。
import { IsArray } from 'class-validator';

class ExampleDto {
  @IsArray()
  hobbies: string[];
}
  1. @IsOptional():指定屬性是可選的,允許其值為 nullundefined
import { IsOptional } from 'class-validator';

class ExampleDto {
  @IsOptional()
  bio?: string;
}
  1. @IsNotEmptyObject():確保屬性的值是一個非空物件。
import { IsNotEmptyObject } from 'class-validator';

class ExampleDto {
  @IsNotEmptyObject()
  data: Record<string, any>;
}

這些是 class-validator 中的一些其他常用驗證器,可以幫助你確保數據的合法性。當然,還有其他更多的驗證器可以在官方文件中找到,具體選擇取決於你的應用程序的需求。

希望這能對你有所幫助!如果你有任何進一步的問題,請隨時提出。

自訂驗證器

一開始介紹有提到,也可以自製驗證器,卻沒有說自訂驗證器要怎麼做
https://ithelp.ithome.com.tw/upload/images/20231028/20135750f7eB92DTsl.png

class-validator 中,你可以創建自己的自訂驗證器來滿足特定需求。這種自訂驗證器可以應用於屬性上,並允許你使用自定義的邏輯進行驗證。

以下是創建自訂驗證器的步驟:

步驟一:創建自訂驗證器

首先,你需要創建一個類別來表示自訂驗證器。這個類別應該實現 ValidatorConstraintInterface 接口,並且需要包含一個 validate 方法,這個方法將包含你的自定義驗證邏輯。

import { ValidatorConstraint, ValidatorConstraintInterface, ValidationArguments } from 'class-validator';

@ValidatorConstraint({ name: 'customText', async: false })
export class CustomTextValidator implements ValidatorConstraintInterface {
  validate(text: string, args: ValidationArguments) {
    // 自定義驗證邏輯在這裡實現
    return text.includes('custom');
  }

  defaultMessage(args: ValidationArguments) {
    // 如果驗證失敗時返回的錯誤訊息
    return `${args.property} must contain the word 'custom'`;
  }
}

步驟二:應用自訂驗證器

接著,你需要在屬性上應用這個自訂驗證器,可以使用 @ValidatorConstraint 裝飾器來進行標記。

import { IsString, Validate } from 'class-validator';
import { CustomTextValidator } from './custom-text.validator';

class ExampleDto {
  @IsString()
  @Validate(CustomTextValidator) // 應用自訂驗證器
  description: string;
}

步驟三:使用自訂驗證器

當進行驗證時,class-validator 將自動調用你的自訂驗證器的 validate 方法來進行驗證。

import { validate } from 'class-validator';
import { ExampleDto } from './example.dto';

const exampleDto = new ExampleDto();
exampleDto.description = 'This is a custom description';

validate(exampleDto).then(errors => {
  if (errors.length > 0) {
    console.log('Validation failed. Errors:', errors);
  } else {
    console.log('Validation succeeded');
  }
});

這樣,當 description 不包含字串 'custom' 時,驗證將失敗。

步驟四:定義自訂錯誤訊息

在自訂驗證器中,你可以通過實現 defaultMessage 方法來定義驗證失敗時返回的錯誤訊息。

這就是如何創建並應用自訂驗證器。通過這種方式,你可以根據特定需求定義自己的驗證邏輯,以確保數據的合法性。希望這能幫到你!如果你有任何進一步的問題,請隨時提出。

小練習&實作

驗證一下,把剛剛的程式碼改成這樣 (只用了非空、必須是字串)

import { Controller, Get, Query } from '@nestjs/common';
import { AppService } from './app.service';
import { IsNotEmpty, IsString } from 'class-validator';
export class TestRequestDTO {
  @IsNotEmpty()
  @IsString()
  name: string;
}

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

  @Get('test')
  async getTest(@Query() query: TestRequestDTO): Promise<string> {
    return `get hello!! ${query.name}`;
  }
}

果然沒帶入參數,就得到400的錯誤回報
https://ithelp.ithome.com.tw/upload/images/20231028/20135750uWTtPttAAO.png

陣列巢狀驗證: @ValidateNested({ each: true })

上列的都是單一的物件類別,頂多驗證「IsArray」,確保是一個陣列
但是陣列裡面的結構,如果是一個物件呢?或是陣列裡面的物件又有陣列呢?

來問問ChatGPT怎麼處理陣列內的物件驗證
https://ithelp.ithome.com.tw/upload/images/20231028/20135750O6lFvyB0aL.png

如果 DTO 中的某個屬性是陣列,而且你想要確保陣列中的每個元素都符合特定的物件型別,你可以使用 @ValidateNested() 裝飾器,並搭配自定義的自訂驗證器。

讓我為你提供一個示例:

假設你有一個 DTO,其中有一個屬性是一個包含 User 物件的陣列:

import { IsArray, ValidateNested } from 'class-validator';
import { Type } from 'class-transformer';
import { User } from './user.entity';

class CreateUserDto {
  @IsArray()
  @ValidateNested({ each: true }) // 使用 @ValidateNested({ each: true }) 指示驗證器對每個元素進行驗證
  @Type(() => User) // 使用 @Type(() => User) 告訴驗證器要對每個元素使用 User 類別的驗證規則
  users: User[];
}

在這個例子中,我們使用了 @IsArray() 裝飾器來確保 users 是一個陣列。然後,我們使用 @ValidateNested({ each: true }) 指示驗證器對陣列中的每個元素進行驗證。最後,我們使用 @Type(() => User) 告訴驗證器要使用 User 類別的驗證規則。

接著,你需要確保 User 類別中有相應的驗證裝飾器。這可以參考前面提到的常見驗證器的使用方式。

請確保你在 DTO 中引入了相關的類別和裝飾器。

這樣,當你使用這個 DTO 進行驗證時,class-validator 將會對 users 陣列中的每個元素應用 User 類別的驗證規則。

希望這能幫到你!如果你有任何進一步的問題,請隨時提出。

好的,我們注意到他除了引入class-validator 的function之外
還引入了另外一個函式庫:class-transformer ,使用裡面的@Type()

最一開始的安裝指令教學裡面,也有提到class-transformer :

步驟一:安裝相關套件

首先,你需要安裝 class-validatorclass-transformer 這兩個套件。你可以在你的 NestJS 專案中使用以下指令進行安裝:

npm install class-validator class-transformer

所以在一開始我們就安裝好了這個東西
接下來請ChatGPT介紹有關class-transformer 這個套件

class-transformer

https://ithelp.ithome.com.tw/upload/images/20231028/20135750DU5fJsxJwd.png

class-transformer 是一個用於將類別實例轉換為其他形式(例如:JSON、其他類別的實例等)的庫。它通常與 class-validator 一起使用,用於將 DTO 轉換為實體類別,並進行驗證。

以下是 class-transformer 的詳細介紹:

1. 類別轉換

class-transformer 提供了一個 classToPlain 方法,可以將一個類別實例轉換為普通的 JavaScript 物件或 JSON。

import { classToPlain } from 'class-transformer';

class User {
  id: number;
  username: string;
}

const user = new User();
user.id = 1;
user.username = 'john_doe';

const plainUser = classToPlain(user);
console.log(plainUser);
// Output: { id: 1, username: 'john_doe' }

2. 從普通物件轉換為類別實例

class-transformer 也提供了一個 plainToClass 方法,可以將普通的 JavaScript 物件轉換為指定類別的實例。

import { plainToClass } from 'class-transformer';

class User {
  id: number;
  username: string;
}

const plainUser = { id: 1, username: 'john_doe' };
const userInstance = plainToClass(User, plainUser);

console.log(userInstance instanceof User); // Output: true

3. 嵌套轉換

class-transformer 允許你在轉換過程中處理嵌套的對象。

import { plainToClass } from 'class-transformer';

class Address {
  street: string;
  city: string;
}

class User {
  id: number;
  username: string;
  address: Address;
}

const plainUser = { id: 1, username: 'john_doe', address: { street: '123 Main St', city: 'New York' } };
const userInstance = plainToClass(User, plainUser);

console.log(userInstance.address instanceof Address); // Output: true

4. 從類別實例中排除屬性

你可以使用 @Exclude() 裝飾器來指定哪些屬性在轉換過程中應該被排除。

import { Exclude } from 'class-transformer';

class User {
  id: number;
  username: string;

  @Exclude()
  password: string;
}

const user = new User();
user.id = 1;
user.username = 'john_doe';
user.password = 'secret_password';

const plainUser = classToPlain(user);
console.log(plainUser);
// Output: { id: 1, username: 'john_doe' }

這些都是 class-transformer 提供的一些基本功能,它可以幫助你方便地進行類別實例與其他形式之間的轉換,這對於在 NestJS 中處理 DTO 和實體類別之間的轉換非常有用。

希望這能幫到你!如果你有任何進一步的問題,請隨時提出。

前面使用DTO的時候也沒有要我們這樣轉換,什麼時候會需要這樣轉換呢?
https://ithelp.ithome.com.tw/upload/images/20231028/20135750PpYde58hGE.png

class-transformer 的常見用途和應用包括以下幾個方面:

  1. DTO 轉換:

    在 NestJS 或其他後端框架中,當你接收到來自客戶端的請求時,通常會使用 DTO(Data Transfer Object)來描述請求或回應的格式。這時,你可以使用 class-transformer 將接收到的 JSON 數據轉換為相應的 DTO 實例,或將 DTO 實例轉換為 JSON 以作為回應。

    例如:

    import { plainToClass } from 'class-transformer';
    import { CreateUserDto } from './dto/create-user.dto';
    
    // 在控制器中
    const createUserDto = plainToClass(CreateUserDto, request.body);
    
  2. 數據模型轉換:

    在應用程序中,可能會有不同的數據模型表示同一類型的數據。使用 class-transformer 可以方便地在這些模型之間進行轉換,使得應用程序中的不同模組可以使用統一的數據格式。

    例如:

    import { plainToClass } from 'class-transformer';
    import { UserViewModel } from './view-models/user.view-model';
    import { UserEntity } from './entities/user.entity';
    
    const userEntity = new UserEntity();
    userEntity.id = 1;
    userEntity.username = 'john_doe';
    
    const userViewModel = plainToClass(UserViewModel, userEntity);
    
  3. 過濾數據:

    有時候你可能只需要數據模型的一部分,或者需要排除某些數據。這時,你可以使用 @Exclude() 裝飾器來排除不需要的屬性。

    例如:

    import { Exclude } from 'class-transformer';
    
    class UserEntity {
      id: number;
      username: string;
    
      @Exclude()
      password: string;
    }
    
  4. 數據轉換與驗證:

    class-transformer 可以與 class-validator 一起使用,這樣你可以將接收到的數據轉換為類別實例並進行驗證,以確保數據的合法性。

    例如:

    import { plainToClass } from 'class-transformer';
    import { validate } from 'class-validator';
    import { CreateUserDto } from './dto/create-user.dto';
    
    const createUserDto = plainToClass(CreateUserDto, request.body);
    const errors = await validate(createUserDto);
    

總的來說,class-transformer 主要用於在不同數據表達形式之間進行轉換,使得應用程序中的不同模塊可以方便地交換數據,同時也可以應用於數據的過濾和驗證等場景。當你需要在不同數據形式之間進行轉換時,class-transformer 可以是一個非常方便的工具。

希望這能幫到你!如果你有任何進一步的問題,請隨時提出。

踩坑:Get Query 型別錯誤!boolean判斷失敗

學完了以上,走沒幾步路馬上就踩到坑了

事情是這樣的,做了一個GET Method的api,裡面有一個參數是boolean值
在app.controller.ts中,改成下列這樣

import { Controller, Get, Query } from '@nestjs/common';
import { plainToClass } from 'class-transformer';
import { IsBoolean } from 'class-validator';
import { AppService } from './app.service';

export class TestRequest {
  @IsBoolean()
  isCheck: boolean;
}
@Controller()
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Get('test')
  async test(@Query() query: TestRequest): Promise<string> {
    const body = plainToClass(TestRequest, query);
    console.log(body);
    if (body.isCheck) return `is Checked, isCheck = ${body.isCheck}`;
    return 'no checked';
  }
}

如果參數中的isCheck是false,那應該要回應「no checked」

第一翻:驗證失敗,無法判斷為boolean value

https://ithelp.ithome.com.tw/upload/images/20231028/201357507ZCkpW1rPf.png

好那我們先把@IsBoolean()拿掉,變成

export class TestRequest {
  isCheck: boolean;
}

第二翻:任何類型全部變成字串

https://ithelp.ithome.com.tw/upload/images/20231028/20135750vjMDgWOES2.png

判斷走到判定為true的邏輯去了,居然顯示 「is Checked」

再看看server這邊console.log出來的
https://ithelp.ithome.com.tw/upload/images/20231028/20135750P8oumnR7HA.png
拿到的居然是一個 { isCheck: 'false'}
所以難怪檢查有@IsBoolean()的時候,他判斷我們沒有傳入boolean值啊

繼續跟ChatGPT求救
https://ithelp.ithome.com.tw/upload/images/20231028/20135750EENSIb3AxD.png

這個問題可能是因為在 Express 中,從 URL 參數(Query)獲取的值都是字串,而你的 isCheck 屬性在 TestRequest 類別中定義為布林值。因此,class-transformer'false' 轉換為字串而不是布林值。

為了解決這個問題,你可以在 TestRequest 類別中使用類型轉換來確保 isCheck 的類型是布林值。你可以使用 @Type(() => Boolean) 來指示 class-transformer 將這個屬性轉換為布林值。

這樣修改你的程式碼:

import { Controller, Get, Query } from '@nestjs/common';
import { plainToClass } from 'class-transformer';
import { IsBoolean } from 'class-validator';
import { Type } from 'class-transformer';
import { AppService } from './app.service';

export class TestRequest {
  @IsBoolean()
  @Type(() => Boolean) // 將屬性轉換為布林值
  isCheck: boolean;
}

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

  @Get('test')
  async test(@Query() query: TestRequest): Promise<string> {
    const body = plainToClass(TestRequest, query);
    console.log(body);
    if (body.isCheck) return `is Checked, isCheck = ${body.isCheck}`;
    return 'no checked';
  }
}

這樣,isCheck 屬性將會被正確地轉換為布林值,並且在驗證時也不會出現錯誤。

希望這能解決你的問題!如果你有任何進一步的問題,請隨時提出。

加上去之後,可以跑了,但是!
https://ithelp.ithome.com.tw/upload/images/20231028/20135750PUIocMoYxq.png

仔細看看,@Type把我們的false,硬生生轉成true啦

這樣不對吧大哥! 確實如果在js中 使用 if ('false') console.log('true!')
裡面的內容會被判定為true,然後執行if內部的程序

但不對啦大哥,這樣我一輩子傳非空內容都會變成true,失去判斷能力了!
https://ithelp.ithome.com.tw/upload/images/20231028/20135750hhnOTBmrBo.png

這可能是因為當 isCheck 為字串 'false' 時,JavaScript 會將其解釋為布林值 true。這是 JavaScript 的行為,這也是為什麼在類型轉換時會發生這種情況。

為了確保 isCheck 參數被正確地轉換成布林值,你可以在 TestRequest 中的 isCheck 屬性上使用自訂的轉換器來處理它。這樣你可以在轉換過程中將字串 'false' 正確轉換成布林值 false

這是如何做的範例:

import { Controller, Get, Query } from '@nestjs/common';
import { plainToClass, Transform } from 'class-transformer';
import { IsBoolean } from 'class-validator';
import { Type } from 'class-transformer';
import { AppService } from './app.service';

export class TestRequest {
  @IsBoolean()
  @Transform(({ value }) => value === 'true') // 使用自訂轉換器處理值
  isCheck: boolean;
}

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

  @Get('test')
  async test(@Query() query: TestRequest): Promise<string> {
    const body = plainToClass(TestRequest, query);
    console.log(body);
    if (body.isCheck) return `is Checked, isCheck = ${body.isCheck}`;
    return 'no checked';
  }
}

這樣做會在轉換過程中檢查參數是否為字串 'true',並將其轉換為布林值。希望這能解決你的問題!如果你有任何進一步的問題,請隨時提出。

這樣還是有一個問題啊!就是變成不管來的東西是什麼,只看是不是'true'
今天我給參數 isCheck=t ,也不會收到400的request錯誤
https://ithelp.ithome.com.tw/upload/images/20231028/20135750cPTuY5kVVf.png

還有一個大問題就是都要手動轉換,這個也是很有問題的事情
今天如果我忘記get method的參數型別,會強制變成字串
在開發新API的時候,可能一樣的坑又要再踩一次
https://ithelp.ithome.com.tw/upload/images/20231028/20135750kHkVOlUfO6.png

對於在 NestJS 中處理 GET 請求中的布林參數,NestJS 在這方面提供了一些內建的解決方案,而不需要手動進行轉換。你可以使用 ParseBoolPipe 來處理布林參數的自動轉換和驗證。

以下是如何使用 ParseBoolPipe 的示例:

import { Controller, Get, Query, ParseBoolPipe } from '@nestjs/common';

@Controller()
export class AppController {
  @Get('test')
  async test(@Query('isCheck', ParseBoolPipe) isCheck: boolean): Promise<string> {
    if (isCheck) return `is Checked, isCheck = ${isCheck}`;
    return 'not checked';
  }
}

在這個示例中,我們使用 ParseBoolPipe 來處理 isCheck 參數,它會自動將字符串 'true' 轉換為布林值 true,字符串 'false' 轉換為布林值 false,同時也處理了其他可能的布林值字符串。如果參數無法解析為布林值,它將返回 Bad Request 錯誤(HTTP 400)。

這樣你不需要手動進行類型轉換和驗證,NestJS 會自動處理。

希望這能解決你的問題!如果你有任何進一步的問題,請隨時提出。

雖然可以正常運作了,但這仍然不是我心中的最佳解
畢竟就像我上面說的,每次撰寫GET Method的API的時候的必須記得做這個轉換
單獨針對要處理的query抽出,並沒有很直覺方便

後來查了一下網路,目前NestJS似乎…真的就存在這個問題?
如果有更好的解法可以在下面留言回應跟我說!我會很感激的QQ

該來收個尾了!倒數第二個問題!
為什麼DTO都使用class,而不是typeScript用來定義型別的interface?

DTO: class vs interface

https://ithelp.ithome.com.tw/upload/images/20231028/20135750fpSoOV6P3A.png

在 NestJS 和相關框架中,你可以使用 classinterface 來定義 DTO(Data Transfer Object),但使用 class 通常更有優勢,原因如下:

  1. 驗證和轉換: 使用 class 定義的 DTO 可以受益於 class-validatorclass-transformer 這兩個庫。你可以在 DTO 中使用驗證裝飾器來確保輸入數據的合法性,同時使用轉換器進行類型轉換。這使得處理輸入和輸出數據變得更容易,並且可以減少冗長的驗證代碼。
  2. 更具表達性: class 可以包含屬性、方法和自定義邏輯,這使得 DTO 可以更豐富地描述數據結構和行為。這對於描述複雜的請求或回應結構非常有用。
  3. 實例化和初始化: class 可以實例化,這對於在處理輸入數據時非常有用。當你從客戶端接收到請求時,你可以通過實例化 DTO 並將數據填充到實例中,這比使用 interface 要更容易。
  4. 可讀性: class 具有更好的可讀性,可以明確定義數據結構和預期的行為。這對團隊協作和代碼維護非常重要。

雖然你可以使用 interface 來定義 DTO,但通常 class 更為常見,因為它提供了更多功能和優勢,尤其在需要進行驗證、轉換和實例化時。但請注意,這不是一個硬性規定,你可以根據你的項目需求選擇適合的方式。

總之,使用 class 來定義 DTO 通常是更強大且更有優勢的選擇,特別是在需要更多功能和驗證的情況下。

R~也就是說,用interface就會和我們一開始一樣,只是單純定義typescript型別

但是不會有任何驗證的功效,無法搭配class-validatorclass-transformer
也無法實例化、無法定義一些方法

統整重點

https://ithelp.ithome.com.tw/upload/images/20231028/20135750zdXJ54i3D6.png

結合前面的討論,以下是有關 DTO、class-validator 和 class-transformer 的重點:

DTO(Data Transfer Object):

  • DTO 是一種用於描述請求或回應數據格式的模型。
  • 可以使用 classinterface 來定義 DTO,但通常建議使用 class
  • 使用 class 可以受益於 class-validatorclass-transformer 库,便於數據的驗證和轉換。

class-validator:

  • class-validator 提供了一系列的裝飾器,可以應用於 DTO 屬性上,用於進行數據的驗證。
  • 驗證器可以確保數據符合特定的標準,例如是否為數字、是否為字串等。
  • 也可以自定義驗證器來滿足特定需求。

class-transformer:

  • class-transformer 是一個用於在類別實例和其他形式(例如 JSON)之間進行轉換的庫。
  • 可以將類別實例轉換為普通的 JavaScript 物件或 JSON,也可以從普通的 JavaScript 物件轉換為類別實例。
  • 可以處理嵌套轉換和過濾數據等需求。

結語:

在 NestJS 開發中,合理使用 DTO、class-validator 和 class-transformer 可以使代碼更為清晰、可讀性更高,同時也使得數據的處理更為便捷和安全。選擇適當的驗證器和轉換器,能夠為項目的開發和維護帶來許多便利。

總的來說,合理利用這些工具能夠讓你的應用程式更穩健、更容易擴展,也能為開發團隊提供更好的協作環境。希望這些資訊對你在 NestJS 開發中有所幫助!如有更多疑問,請隨時提出。

結語

本次對話紀錄連結在這邊:
https://chat.openai.com/share/80e4a6bb-2cca-47c8-8b4f-3f38924ff29c

坑!Controller 裡面也是滿坑滿谷的坑!
兩篇還講不完Controller !
雖然這篇也有默默地帶到一些中間件的概念,但等下一篇再來細細討論

最後抱怨一件事情,好像是2023/10/27開始
ChatGPT每問一個問題就要機器人驗證!像這樣!
https://ithelp.ithome.com.tw/upload/images/20231028/20135750Pq8dfDQg8s.png

問一次如果可以撐一小時我覺得很好,但是每個問題都要問我
驗證到發瘋。希望ChatGPT可以趕快修正驗證頻率,不然我很難做事RRRRRR

OK抱怨完畢。

我是被Get方法的boolean型別參數弄得咪咪冒冒的前端工程師一宵三筵,我們明天見!


上一篇
【Day43】ChatGPT請教教我:NestJS!(二)- Controller & 裝飾器
下一篇
【Day45】ChatGPT請教教我:NestJS!(四)- Controller & Middleware!
系列文
用ChatGPT詠唱來完成工作與點亮前後端技能樹56
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言