[NestJS] 大前端Node層應用框架 之 NestJS英文官方文檔解讀筆記 (持續跟新ing

前言

公司的項目使用了大前端技術,大前端做爲中間層,整合了服務端的接口,也縮短了前端的開發等待接口的空窗期。全部的mock數據在node層生成供調試,使前端開發造成了閉環。而node層所運用的框架中,nest是一個很是高效、好用的框架。javascript

  • 本文做爲學習nest框架的一篇筆記,對英文官方文檔進行了直譯、意譯和理解。
  • 若有錯誤歡迎及時指正並修改。

介紹

  • NestJS是一個爲了構建高效、可擴展的服務端應用框架。它運用了漸進式javascript,使用而且徹底支持Typescript,結合了面向對象編程OOP、函數式編程FP、函數式響應編程FRP。
  • 在Nest底層也運用了強大的HTTP服務框架例如Express(默認),和Fastify(可配置)。
  • Nest在這些經常使用的HTTP框架之上提供了一個抽象層,但也直接暴露了他們本身的API給開發者。這也容許開發者自由地使用無數原先平臺就可使用的第三方模塊(中間件)。

安裝

  • 開始一個nest,可使用腳手架Nest CLI,也能夠clone一個初始項目
$ npm i -g @nestjs/cli
$ nest new project-name
複製代碼

or前端

$ git clone https://github.com/nestjs/typescript-starter.git project
$ cd project
$ npm install
$ npm run start
複製代碼
  • 手動建立
$ npm i --save @nestjs/core @nestjs/common rxjs reflect-metadata
複製代碼

控制器

控制器

  • 控制器負責處理收到的請求而且返回響應給客戶端
  • 控制器的目的是接受應用的具體請求。路由決定了哪個控制器接受哪些請求。一般控制器都會至少兩個路由,不一樣的路由能夠執行不一樣的動做。
  • 建立一個控制器須要使用類class和裝飾器decorator


路由

  • 建立一個基本的控制器須要使用裝飾器@Controller,下例規定了路由前綴cat傳入控制器。如此一來咱們能夠輕鬆地將一系列路由歸爲一組,減小了重複代碼。後面全部路由都會在/cats之下。
import { Controller, Get } from '@nestjs/common';

@Controller('cats')
export class CatsController {
  @Get()
  findAll(): string {
    return 'This action returns all cats';
  }
}
複製代碼
  • 建立控制器可使用腳手架指令$ nest g controller cats
  • @Get()告訴Nest須要爲HTTP的get請求建立一個處理函數。
  • 此例中,Nest路由了一個get請求到findAll方法,而此方法名徹底是隨意的(顯然咱們必需爲路由綁定一個處理函數,然而Nest歷來不關心這個函數的名字到底叫什麼)。而且這個方法將會返回一個200的狀態碼和對應的響應數據(此例爲字符串)。

    Nest使用了兩個不一樣的配置來操做處理函數的響應

standard(標準的) 此配置使用內置的方法,當函數返回了一個對象或數組會自動轉爲JSON格式,而返回字符時不會轉換。這使得返回值處理十分簡單,而Nest只關注剩餘的部分。
Library-specific(規定庫的) 使用此配置能夠經過在函數簽名中注入@Res()裝飾器來獲取庫的(Express)request對象(findAll(@Res() response)),這樣也就可使用原生的request對象暴露的API進行處理。例如response.status(200).send()

不能夠同時使用上述兩種方法,當Nest監測到你使用了@Res()或者@Next()時,那麼代表你選擇了Library-specific項。二者同時使用時Nest會自動禁用standard項,而形成不可預期的後果。 java


request對象

  • 處理函數一般須要獲取客戶端請求的詳細信息,Nest提供了底層庫的request對象(默認Express)。
  • 方法是在處理函數簽名中注入@Req()裝飾器
import { Controller, Get, Req } from '@nestjs/common';
import { Request } from 'express';

@Controller('cats')
export class CatsController {
  @Get()
  findAll(@Req() request: Request): string {
    return 'This action returns all cats';
  }
}
複製代碼
ps: 爲了使用Express的類型能夠安裝`@types/express`包
複製代碼
  • request對象表明了Http請求,包含許多屬性:query string、parameters、HTTP headers。大可能是狀況下這些屬性不用咱們手動去調用,可使用專門的裝飾器。例如@Body()@Query()
@Request() req
@Response() res*
@Next() next
@Session() req.session
@Param(key?: string) req.params / req.params[key]
@Body(key?: string) req.body / req.body[key]
@Query(key?: string) req.query / req.query[key]
@Headers(name?: string) req.headers / req.headers[name]

* 因爲Library-specific的存在,會有兩種風格的Resquest對象。standard模式使用@Request()裝飾器,而底層庫的request對象使用@Req()裝飾器獲取,並確保理解他們的不一樣。node


資源

  • 前面咱們經過Get請求來獲取了cat的資源,如今咱們也但願去建立一個新的cat記錄。使用post。
import { Controller, Get, Post } from '@nestjs/common';

@Controller('cats')
export class CatsController {
  @Post()
  create(): string {
    return 'This action adds a new cat';
  }

  @Get()
  findAll(): string {
    return 'This action returns all cats';
  }
}
複製代碼
  • Nest提供了一樣風格的其餘HTTP請求方式,@Put()@Delete()@Patch()@Options()@Head()@All()

路由通配符

  • *號通配符能夠在路由中使用,能夠匹配任意字符的結合
@Get('ab*cd')
findAll() {
  return 'This route uses a wildcard';
}
複製代碼
  • 上例能夠匹配abcd, ab_cd, abecd等等
  • 正則表達式,以及其餘符號?+*()都可使用
  • The hyphen ( -) and the dot (.) are interpreted literally by string-based paths.git


狀態碼

  • 處理函數響應的狀態碼老是默認爲200,post例外爲201
  • 可使用裝飾器@HttpCode(...)指定狀態碼
@Post()
@HttpCode(204)
create() {
  return 'This action adds a new cat';
}
複製代碼
  • 一般狀態碼不是靜態,而是由多種因素來決定的。在這種狀況下你可使用Library-specific模式的response對象

請求頭

  • 想要規定一個自定義的響應頭,可使用裝飾器@Header()或者Library-specific模式的res對象
@Post()
@Header('Cache-Control', 'none')
create() {
  return 'This action adds a new cat';
}
複製代碼

路由參數

  • 若是須要接受動態的數據做爲請求地址的一部分,靜態地址的路由將不會工做。(動態路由參數)
  • 例如Get /cats/1 來獲取id=1的Cat資源
  • 使用裝飾器@Param()來獲取動態路由參數
@Get(':id')
findOne(@Param() params): string {
  console.log(params.id);
  return `This action returns a #${params.id} cat`;
}
複製代碼
  • 也能夠傳入特定的參數給裝飾器,來直接經過名稱獲取路由參數。
@Get(':id')
findOne(@Param('id') id): string {
  return `This action returns a #${id} cat`;
}
複製代碼

路由順序

  • 請注意路由順序,當以前使用了動態路由參數進行截獲,則以後就不會命中不使用動態參數的靜態同級路由。
@Controller('cats')
export class CatsController {
  @Get(':id')
  findOne(@Param('id') id: string) {
    return `This action returns a #${id} cat`;
  }

  @Get()
  findAll() {
    // 此節點將不會命中,由於/cats請求將被/cats:id請求所截獲
  }
}
複製代碼

做用域

  • 因爲讀者來自不一樣的編程語言背景,學習此塊內容是不被指望的,幾乎全部的東西都經過收到的request請求共享了。咱們有一個鏈接池,鏈接到了數據庫、具備全局狀態的單例服務等。記住Node並無遵循多線程無狀態的請求/響應模式,全部的請求都爲單線程處理。所以使用單例對象對於應用來更加安全。

異步性

  • 數據獲取幾乎都是異步的,而Nest支持異步async函數。
  • 每一個異步async函數都返回一個Promise對象,也就是你能夠返回一個延遲的值,Nest將會本身處理。
@Get()
async findAll(): Promise<any[]> {
  return [];
}
複製代碼
  • 也可使用RxJS

請求的負載

  • 若是使用了Typescript,那麼須要定義一個DTO文件(Data Transfer Object)。DTO文件定義了數據在網絡中的傳輸形式。可使用Typescript的interface或者簡單的一個類來定義DTO文件。建議使用類來定義,由於類是ES6的一部分,他們在js編譯的過程當中是受保護的實體信息,而interface在轉譯爲js的過程當中會被移除,Nest在運行過程當中獲取不到它們。這很重要,由於一些特性,例如管道,在運行時可能會使變量的類型改變。
export class CreateCatDto {
  readonly name: string;
  readonly age: number;
  readonly breed: string;
}
複製代碼
@Post()
async create(@Body() createCatDto: CreateCatDto) {
  return 'This action adds a new cat';
}
複製代碼

完整的例子

import { Controller, Get, Query, Post, Body, Put, Param, Delete } from '@nestjs/common';
import { CreateCatDto, UpdateCatDto, ListAllEntities } from './dto';

@Controller('cats')
export class CatsController {
  @Post()
  create(@Body() createCatDto: CreateCatDto) {
    return 'This action adds a new cat';
  }

  @Get()
  findAll(@Query() query: ListAllEntities) {
    return `This action returns all cats (limit: ${query.limit} items)`;
  }

  @Get(':id')
  findOne(@Param('id') id: string) {
    return `This action returns a #${id} cat`;
  }

  @Put(':id')
  update(@Param('id') id: string, @Body() updateCatDto: UpdateCatDto) {
    return `This action updates a #${id} cat`;
  }

  @Delete(':id')
  remove(@Param('id') id: string) {
    return `This action removes a #${id} cat`;
  }
}
複製代碼
  • 控制器一般都屬於一個模塊,咱們要在裝飾器@Module()中包含controllers數組
  • 啓動時,須要在模塊上掛載定義的控制器,不然Nest根本不知道他們的存在。。。
import { Module } from '@nestjs/common';
import { CatsController } from './cats/cats.controller';

@Module({
  controllers: [CatsController],  // 數組
})
export class AppModule {}
複製代碼


供應者

供應者Provider(依賴)

  • 供應者在Nest中一是一個基本的概念。許多基礎的Nest類均可以看做供應者——服務、庫、工廠、工具等等。供應者主要的目的是注入依賴。這意味着對象之間能夠建立多種依賴關係,對象上實例方法的預編譯能夠委託給Nest的運行系統。供應者Provider是一個簡單的使用了裝飾器@Injectable()的類。

  • 在前面的章節,咱們建立了一個簡單的控制器CatController。控制器應只處理HTTP請求,而將其餘複雜的業務交給供應者來處理。

    ps:儘管Nest支持用一種更加面向對象的方式來設計組織依賴,但咱們強烈建議遵循SOLID原則。(面向對象設計的五大原則)

服務

  • 建立一個簡單的服務,來負責數據的存儲和檢索,最後由控制器來調用。
import { Injectable } from '@nestjs/common';
import { Cat } from './interfaces/cat.interface';

@Injectable()
export class CatsService {
  private readonly cats: Cat[] = [];

  create(cat: Cat) {
    this.cats.push(cat);
  }

  findAll(): Cat[] {
    return this.cats;
  }
}
複製代碼

ps: 可使用腳手架命令nest g service cat來建立服務。github

  • 咱們的CatService是擁有一個屬性兩個方法的基本的類,新的特徵就是使用了裝飾器@Injectable()。附帶了元數據來告訴Nest這個類是一個Nest的提供者。順帶一提,此例也使用了一個Cat接口,也許長這樣。
export interface Cat {
  name: string;
  age: number;
  breed: string;
}
複製代碼
  • 如今咱們擁有了一個服務的類來檢索Cats,將它應用到控制器上
import { Controller, Get, Post, Body } from '@nestjs/common';
import { CreateCatDto } from './dto/create-cat.dto';
import { CatsService } from './cats.service';
import { Cat } from './interfaces/cat.interface';

@Controller('cats')
export class CatsController {
  constructor(private readonly catsService: CatsService) {}

  @Post()
  async create(@Body() createCatDto: CreateCatDto) {
    this.catsService.create(createCatDto);
  }

  @Get()
  async findAll(): Promise<Cat[]> {
    return this.catsService.findAll();
  }
}
複製代碼
  • CatService服務經過類的構造器來注入,注意private readonly語法,這種表達式能夠在馬上這個位置同時聲明並初始化成員(TS語法糖)。

依賴注入

  • Nest是圍繞你們所熟知的依賴注入而構建的,關於其概念咱們建議閱讀Auglar的官方文檔。
  • 在Nest中,因爲Typescript的存在,管理依賴是很是簡單的,由於他們經過類型來解析。在下面的例子中,Nest將經過建立並返回一個CatService實例來解析CatService(正常狀況下是一個單例對象,若是在別的地方中被請求過,則返回已經存在實例)。依賴解析成功並傳入控制器的構造函數(或者傳給已經聲明的屬性)。
constructor(private readonly catsService: CatsService) {}
複製代碼

做用域(生命週期)

  • 一個提供者Provider的生命週期(做用域)與應用的聲明週期同步。當應用建立時,全部的依賴必須被解析,所以每一個提供者也必須被實例化。同理,當應用關閉時,每一個提供者將會銷燬。固然你也可使你提供者的生命週期在你的所需範圍內。點擊這裏來閱讀此技巧

自定義化提供者

  • Nest有一個內置的控制反轉(面向對象設計原則,下降耦合性)的容器來處理兩個提供者間的關係。該特性是構成上述的依賴注入的基礎,但實際上它比咱們所描述的還要強大得多。裝飾器@Injectable()只是冰山一角,而且也不是定義供應者的惟一方式。實際上你可使用簡單的值、類、甚至是同步或異步的工廠。點擊這裏閱讀更多案例

可選的提供者

  • 有時候你可能有一些依賴並不須要去解析他。舉個🌰,你的類也許依賴於一個配置對象,可是若是沒有任何入參,應使用默認值。這種狀況下提供者變爲可選的,並且缺乏配置的提供者也不會報錯。
import { Injectable, Optional, Inject } from '@nestjs/common';

@Injectable()
export class HttpService<T> {
  constructor(
    @Optional() @Inject('HTTP_OPTIONS') private readonly httpClient: T
  ) {}
}
複製代碼

以屬性爲基礎的注入

  • 目前爲止咱們使用的都是以構造器爲基礎的注入,提供者經過構造函數被注入。在一些特定狀況下,以屬性爲基礎的注入會更加有用。例如,一個頂層的類依賴於一個或多個提供者,而子類始終經過super來繼承父類引用的Provider是很是"單調乏味"的。爲了不這種狀況,你能夠在屬性層使用裝飾器@Inject()
import { Injectable, Inject } from '@nestjs/common';

@Injectable()
export class HttpService<T> {
  @Inject('HTTP_OPTIONS')
  private readonly httpClient: T;
}
複製代碼

若是你的類中沒有使用另外的提供者,你應該始終使用以構造器爲基礎的注入 正則表達式


註冊提供者

  • 如今咱們有了一個提供者CatService,咱們須要將它註冊到控制器上才能實現注入。在@Module()模塊中加入你的服務CatService到提供者Providers數組。
import { Module } from '@nestjs/common';
import { CatsController } from './cats/cats.controller';
import { CatsService } from './cats/cats.service';

@Module({
  controllers: [CatsController],
  providers: [CatsService],
})
export class AppModule {}
複製代碼
  • 這樣一來,Nest就能夠處理控制器的提供者們之間的依賴關係了。


模塊

模塊

  • 模塊是一個使用裝飾器@Module()的類,裝飾器提供元數據給Nest來組織應用結構。

  • 每一個應用至少有一個根模塊。根模塊是Nest繪製應用結構的起點,Nest利用內部的數據結構來解析模塊、依賴、以及提供者間的關係。理論上來講,很是小的應用也許只由一個根模塊,但這不是典型例子。咱們想要強調,模塊是強烈推薦的一個高效的方式去組織你的組件。因此絕大多數應用將使用多個模塊,每一個模塊涵蓋了一系列相關的功能。
  • 使用@Module()裝飾一個對象,屬性用來描述這個模塊。
供應者Provider 供應者將會被Nest實例化,而且這些供應者至少在當前模塊中是共享的
控制器Controller 這個屬性定義了在當前模塊中哪些控制器將會被實例化
輸入imports 引入模塊的列表,引入了其餘模塊導出的在此模塊中須要使用的提供者
輸出exports 此模塊所使用的提供者的子集,導出的提供者在導入此模塊的其餘模塊中可用
  • 模塊默認包含了提供者Provider,這意味着你若是想注入一個既不是當前模塊一部分的提供者,也不是導入的模塊所導出的提供者,是不可能的。這樣一來,你就要考慮從一個模塊中導入一些提供者,做爲模塊的公共接口或者API。(一個公共模塊)

特徵模塊

  • 控制器CatsController和服務CatsService是應用的同一塊區域,他們應該放入特徵模塊中。
import { Module } from '@nestjs/common';
import { CatsController } from './cats.controller';
import { CatsService } from './cats.service';

@Module({
  controllers: [CatsController],
  providers: [CatsService],
})
export class CatsModule {}
複製代碼

ps: 可使用腳手架指令nest g module cats來建立模塊。算法

  • 上面,咱們定義了CatsModule在cats.module.ts文件中。而後將全部相關的文件放統一目錄下。最後將它導出到根模塊就能夠了。
import { Module } from '@nestjs/common';
import { CatsModule } from './cats/cats.module';

@Module({
  imports: [CatsModule],
})
export class AppModule {}
複製代碼
  • 而後結構目錄看起來像這樣


共享模塊

  • 在Nest中,模塊默認是一個單例對象,而且你能夠輕鬆的在任何提供者之間共享同一個實例。

  • 每一個模塊自動的成爲共享模塊,只要建立就能夠被任意其餘模塊重用。想像一下若是須要在其餘幾個模塊中共享一個CatService服務的實例,咱們須要經過在模塊中增長exports屬性來導出提供者CatService
import { Module } from '@nestjs/common';
import { CatsController } from './cats.controller';
import { CatsService } from './cats.service';

@Module({
  controllers: [CatsController],
  providers: [CatsService],
  exports: [CatsService]
})
export class CatsModule {}
複製代碼
  • 如今任何其餘模塊只要引入CatModule模塊,就能共享同一個服務實例。

模塊重導出

  • 如上所述,模塊能夠導出他們內部的提供者,此外他們還能夠將引入的模塊從新導出。那麼其餘模塊引入了該模塊時,同時可使用該模塊重導出的模塊共享的提供者。
@Module({
  imports: [CommonModule],
  exports: [CommonModule],
})
export class CoreModule {}
複製代碼

依賴注入

  • 模塊的類也能夠引入提供者
import { Module } from '@nestjs/common';
import { CatsController } from './cats.controller';
import { CatsService } from './cats.service';

@Module({
  controllers: [CatsController],
  providers: [CatsService],
})
export class CatsModule {
  constructor(private readonly catsService: CatsService) {}
}
複製代碼
  • 然而模塊的類自身不能做爲提供者被本身引入造成循環依賴(本身依賴本身)

全局模塊

  • 若是你處處引入同一個模塊是「單調乏味的」。不像在Nest中,Augular的提供者註冊在全局做用域下,只要定義,哪裏均可以用。然而Nest將提供者包含在模塊的做用域下,別的地方沒法使用沒有導出的提供者。
  • 若是你想在哪裏均可以使用一些提供者,使用裝飾器@Global()
import { Module, Global } from '@nestjs/common';
import { CatsController } from './cats.controller';
import { CatsService } from './cats.service';

@Global()
@Module({
  controllers: [CatsController],
  providers: [CatsService],
  exports: [CatsService],
})
export class CatsModule {}
複製代碼
  • 裝飾器@Global()使該模塊在全局做用域下。全局模塊應只被註冊一次在覈心模塊或者根模塊當中。在上例中,其餘模塊使用服務CatService時,不用再引入該模塊。

ps: 不要處處使用全局模塊,他只是爲了減小大量的必需引入。大多數狀況下更好的方式是使用importstypescript


動態模塊

  • Nest的模塊系統中包含了一個特性叫作動態模塊,它能輕易地使你建立一個自定義模塊。下面是一個動態模塊的例子,DatabaseModule
import { Module, DynamicModule } from '@nestjs/common';
import { createDatabaseProviders } from './database.providers';
import { Connection } from './connection.provider';

@Module({
  providers: [Connection],
})
export class DatabaseModule {
  static forRoot(entities = [], options?): DynamicModule {
    const providers = createDatabaseProviders(options, entities);
    return {
      module: DatabaseModule,
      providers: providers,
      exports: providers,
    };
  }
}
複製代碼

ps:forRoot()方法可能會同步或異步地返回一個動態模塊。(經過Promise數據庫

  • 這個模塊定義了一個默認的Provider叫作Connection,依賴於傳入的entitiesoptions對象,暴露了一個提供者provider的集合,好比庫。記住動態模塊擴展(而非重載)了基礎模塊的元數據。這就是從模塊導出靜態聲明的提供者Connection和動態配置庫的提供者的方式。
  • 當你須要動態地註冊並配置你的提供者Providers時,這個重要的特性是很是有用的。只要用這種方式定義,DatabaseModule模塊就能以以下的方式導出並配置。
import { Module } from '@nestjs/common';
import { DatabaseModule } from './database/database.module';
import { User } from './users/entities/user.entity';

@Module({
  imports: [DatabaseModule.forRoot([User])],
})
export class AppModule {}
複製代碼
  • 若是你想反過來重導出一個動態模塊,你能夠在導出數組中,刪除forRoot()函數的調用
import { Module } from '@nestjs/common';
import { DatabaseModule } from './database/database.module';
import { User } from './users/entities/user.entity';

@Module({
  imports: [DatabaseModule.forRoot([User])],
  exports: [DatabaseModule],
})
export class AppModule {}
複製代碼

中間件

中間件

  • 中間件是一個函數,在路由處理函數以前被調用。中間件函數接收了requestresponse對象還有管道函數next()

  • 中間件函數能夠完成如下任務

    1. 運行任何代碼
    2. 修改requestresponse對象
    3. 結束request-response循環
    4. 棧中調用next函數
    5. 若是當前中間件沒有結束request-response循環,即必須調用next函數將控制權交給下箇中間件函數。不然請求會一直處於掛起狀態
  • 你能夠用任何一個函數或者使用了裝飾器@Injectable的類實現中間件。若是是類應當實現NestMiddleware接口,而函數不須要任何特殊的需求。讓咱們使用類來實現一個簡單的中間件。

import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response } from 'express';

@Injectable()
export class LoggerMiddleware implements NestMiddleware {
  use(req: Request, res: Response, next: Function) {
    console.log('Request...');
    next();
  }
}
複製代碼

依賴注入

  • Nest中間件徹底支持依賴注入,就像提供者或控制器,他們能夠注入在同一個模塊中可用的依賴。一般是經過constructor來實現。

應用中間件

  • 在裝飾器@Modules()中沒有中間件的位置,取而代之咱們使用模塊類中的configure()函數來設置他們。包含中間件的模塊必須實現NestModule的接口。讓咱們在根模塊上設置一個LoggerMiddleware的中間件。
import { Module, NestModule, MiddlewareConsumer } from '@nestjs/common';
import { LoggerMiddleware } from './common/middleware/logger.middleware';
import { CatsModule } from './cats/cats.module';

@Module({
  imports: [CatsModule],
})
export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(LoggerMiddleware)
      .forRoutes('cats');
  }
}
複製代碼
  • 在上面的例子中咱們爲在CatsController中定義的路由/cats的處理函數以前設置了中間件LoggerMiddleware。 當咱們在配置中間件時,經過傳入一個包含路由pathrequest方法的對象給forRoutes()來將中間件函數限制爲一個特定的路由。在下面的例子中,注意咱們引入了枚舉類型RequestMethod,斷言請求方法的類型。
import { Module, NestModule, RequestMethod, MiddlewareConsumer } from '@nestjs/common';
import { LoggerMiddleware } from './common/middleware/logger.middleware';
import { CatsModule } from './cats/cats.module';

@Module({
  imports: [CatsModule],
})
export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(LoggerMiddleware)
      .forRoutes({ path: 'cats', method: RequestMethod.GET });
  }
}

複製代碼

路由通配符

  • 支持基於模式的路由。舉個例子,*做爲通配符能夠匹配任意字符結合。
forRoutes({ path: 'ab*cd', method: RequestMethod.ALL });
複製代碼

中間件Consumer

  • MiddlewareConsumer是一個輔助類,它提供了一些內置的方法來管理中間件。他們均可以簡單地使用用鏈式風格連接起來。forRoutes()方法可使用單個字符串,多字符串,一個RouteInfo對象,一個控制器Controller的類,或者甚至是多個控制器的類。大多數狀況下你可能會傳入一個用逗號分割的控制器的列表。下面的例子是單個控制器。
import { Module, NestModule, MiddlewareConsumer } from '@nestjs/common';
import { LoggerMiddleware } from './common/middleware/logger.middleware';
import { CatsModule } from './cats/cats.module';

@Module({
  imports: [CatsModule],
})
export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(LoggerMiddleware)
      .forRoutes(CatsController);
  }
}
複製代碼

ps:apply()方法接受一箇中間件,或者多箇中間件參數。

  • 咱們常常想要在中間件應用中排除某個特定的路由。當用類來定義中間件時(正如咱們到目前爲止所作的,而不是使用可替代的函數中間件),咱們可使用exclude()方法來排除某個特定的路由。這個方法接受一個一個或多個對象來匹配須要排除路由path和方法。
consumer
  .apply(LoggerMiddleware)
  .exclude(
    { path: 'cats', method: RequestMethod.GET },
    { path: 'cats', method: RequestMethod.POST }
  )
  .forRoutes(CatsController);
複製代碼
  • 經過上面的例子,中間件LoggerMiddleware會綁定在CatsController中全部的路由,除了傳入exclude()方法的兩個對象。請記住exclude()方法在函數中間件中沒有做用。此外,exclude方法不會排除那些來自更通用的路由。(好比說通配符)。若是你須要那種級別上的控制,你應當把你路由限制的邏輯直接放在中間件中。例如:接受路由請求而且有條件地應用中間件的邏輯。

函數中間件

  • 咱們用的LoggerMiddleware類仍是很是簡單。他沒有成員,沒有額外的方法,沒有依賴。爲何咱們不定義一個簡單的函數而不是一個類呢。讓咱們將它從類中間件轉換爲函數中間件。
export function logger(req, res, next) {
  console.log(`Request...`);
  next();
};
複製代碼
  • 而後在根路由中去使用它。
consumer
  .apply(logger)
  .forRoutes(CatsController);
複製代碼

ps: 考慮只在不須要任何依賴的狀況下使用函數中間件。


多箇中間件

  • 上面提到的,爲了綁定多箇中間件並依次執行,只須要提供一個逗號分割的apply()方法的調用。
consumer.apply(cors(), helmet(), logger).forRoutes(CatsController);
複製代碼

全局中間件

  • 若是咱們想當即在每一個註冊的路由上綁定中間件,咱們可使用由INestApplication接口提供的use()方法。
const app = await NestFactory.create(AppModule);
app.use(logger);  // 就像Express
await app.listen(3000);
複製代碼

異常過濾器

異常過濾器

  • Nest有一個異常層,負責處理整個應用中全部未處理過的異常。當你一個異常沒有被你的應用的代碼處理,他就會被這一層捕獲。而後自動發送合適的用戶友好型響應。

  • 這個動做由一個內置的全局異常過濾器完成,處理了HttpException類型的異常(及其子類)。當一個異常未被識別(既不是HttpException也不是繼承自HttpException的類),客戶端會接受到以下的JSON的響應。
{
  "statusCode": 500,
  "message": "Internal server error"
}

複製代碼

底層異常

  • 內置的Http異常的類經過@nestjs/common包暴露。
  • CatsController中,咱們有一個findAll()函數(一個Get路由請求)。假設這個函數由於一些緣由拋出了一個異常。
@Get()
async findAll() {
  throw new HttpException('Forbidden', HttpStatus.FORBIDDEN);
}
複製代碼

ps: 咱們這裏使用Http狀態碼,是由@nestjs/common導入的輔助枚舉類型。

  • 當客戶端請求這個節點,響應看起來像這樣
{
  "statusCode": 403,
  "message": "Forbidden"
}
複製代碼
  • HttpException的構造函數接受兩個參數,決定了JSON格式的響應體和各自Http響應的狀態碼。第一個參數是一個類型:string | object。傳入一個字符來自定義錯誤信息。傳入一個屬性爲statuserror的字面量對象爲第一個參數而不是字符,來徹底重載響應體。第二個參數應該是一個實際的Http狀態碼。
@Get()
async findAll() {
  throw new HttpException({
    status: HttpStatus.FORBIDDEN,
    error: 'This is a custom message',
  }, 403);
}
複製代碼

而後響應像這樣

{
  "statusCode": 403,
  "error": "This is a custom message"
}
複製代碼

異常結構

  • 建立本身的異常結構是一個很好的實踐。這意味着你的Http異常應當繼承自基礎的HttpException類。結果是Nest將辨識你的異常,自動處理異常的響應。
export class ForbiddenException extends HttpException {
  constructor() {
    super('Forbidden', HttpStatus.FORBIDDEN);
  }
}
複製代碼
  • 因爲ForbiddenException繼承自HttpException,他將和內置的異常處理函數無縫銜接,因此咱們能夠在findAll()函數中使用
@Get()
async findAll() {
  throw new ForbiddenException();
}
複製代碼

Http Exceptions

  • 爲了減小咱們所寫的重複的代碼,Nest提供了一系列有用的繼承自HttpException的異常。他們都由@nestjs/common包暴露
    • BadRequestException
    • UnauthorizedException
    • NotFoundException
    • ForbiddenException
    • NotAcceptableException
    • RequestTimeoutException
    • ConflictException
    • GoneException
    • PayloadTooLargeException
    • UnsupportedMediaTypeException
    • UnprocessableEntityException
    • InternalServerErrorException
    • NotImplementedException
    • BadGatewayException
    • ServiceUnavailableException
    • GatewayTimeoutException

異常過濾器

  • 內置的異常過濾器能夠自動地爲你處理大部分狀況,可是您可能想要徹底控制異常層。例如你想加入logging或者因爲一些動態的因素返回不一樣的JSON結構的響應。異常過濾器爲這個目的而設計。
  • 讓咱們建立一個異常過濾器來捕獲繼承自HttpException的異常,而且爲他們實現了自定義響應的邏輯。
import { ExceptionFilter, Catch, ArgumentsHost, HttpException } from '@nestjs/common';
import { Request, Response } from 'express';

@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
  catch(exception: HttpException, host: ArgumentsHost) {
    const ctx = host.switchToHttp();
    const response = ctx.getResponse<Response>();
    const request = ctx.getRequest<Request>();
    const status = exception.getStatus();

    response
      .status(status)
      .json({
        statusCode: status,
        timestamp: new Date().toISOString(),
        path: request.url,
      });
  }
}
複製代碼

ps: 全部異常過濾器都必須實現ExceptionFilter<T>接口。這須要你提供catch(exception: T, host: ArgumentsHost)方法和簽名。T代表了異常的類型。

  • @Catch(HttpException)裝飾器爲一場過濾器綁定了所需的元數據,告訴Nest這個過濾器正在尋找HttpException類型的異常。@Catch()裝飾器接受一個參數,或者一個逗號分割的列表。這使你能夠一次性設置多種不一樣類型的異常。

接口Arguments host

  • 讓咱們看一看catch()方法的參數,exception參數是當前正在處理的異常對象,而host是一個ArgumentsHost對象。ArgumentsHost是傳遞給原始請求處理函數的參數的包裝器(異常初始化的地方),包含了一個基於應用類型的特定的數組。
export interface ArgumentsHost {
  getArgs<T extends Array<any> = any[]>(): T;
  getArgByIndex<T = any>(index: number): T;
  switchToRpc(): RpcArgumentsHost;
  switchToHttp(): HttpArgumentsHost;
  switchToWs(): WsArgumentsHost;
}
複製代碼
  • ArgumentsHost提供了一系列便利的方法來幫助咱們在不一樣的應用環境下從基礎數組中選擇正確的參數,ArgumentsHost只不過是一組參數。舉個例子,當過濾器使用的是Http應用環境,ArgumentsHost將會提供一個[request, response]數組。然而噹噹前環境是WebSocket應用環境,它包含一個[client, data]數組。這種方法使你可以訪問在自定義catch()方法中最終傳遞給原始處理函數的任何參數。

綁定過濾器

  • 讓咱們將HttpExceptionFilterCatsControllercreate()函數聯繫起來。
@Post()
@UseFilters(new HttpExceptionFilter())
async create(@Body() createCatDto: CreateCatDto) {
  throw new ForbiddenException();
}
複製代碼

ps: 裝飾器@UseFilters()@nestjs/common包導出。

  • 咱們這裏使用了裝飾器@UseFilters()。相似於@Catch()裝飾器,它接受一個過濾器實例,過着一個逗號分割的過濾器實例的列表。這裏咱們建立了HttpExceptionFilter的實例。或者你也能夠傳入類(而不是實例),將實例化的責任交給框架,而且啓用依賴注入。
@Post()
@UseFilters(HttpExceptionFilter)
async create(@Body() createCatDto: CreateCatDto) {
  throw new ForbiddenException();
}
複製代碼

ps: 儘量地使用類而不是實例來註冊過濾器。它減小了內存使用,由於Nest在你的模塊中能夠輕易地重用同一個類的實例。

  • 在上面的例子中,HttpExceptionFilter僅應用於單個create()路由處理函數,使其具備方法範圍。異常過濾器能夠在不一樣的層肯定做用域:方法做用域、控制器做用域或全局做用域。例如,設置一個控制器做用域的過濾器:
@UseFilters(new HttpExceptionFilter())
export class CatsController {}
複製代碼
  • 此構造函數爲在CatsController中定義的每一個路由處理函數設置了HttpExceptionFilter
  • 建立一個全局做用域的過濾器:
async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useGlobalFilters(new HttpExceptionFilter());
  await app.listen(3000);
}
bootstrap();
複製代碼

`useGlobalFilters()`方法不爲網關或混合應用程序設置過濾器。

  • 全局做用域的過濾器在整個應用當中被使用,每一個控制器每一個路由處理函數。按照依賴注入,從任何模塊外部註冊的全局過濾器(如上例所示,使用useGlobalFilters())不能注入依賴項,由於這是在任何模塊的環境以外完成的。你可使用如下構造直接從任何模塊註冊一個全局範圍的過濾器
import { Module } from '@nestjs/common';
import { APP_FILTER } from '@nestjs/core';

@Module({
  providers: [
    {
      provide: APP_FILTER,
      useClass: HttpExceptionFilter,
    },
  ],
})
export class AppModule {}
複製代碼

ps: 當使用此方法爲過濾器執行依賴項注入時,請注意,不管使用此構造的模塊是什麼,過濾器實際上都是全局的。這應該在哪裏進行?選擇定義過濾器(上面示例中的HttpExceptionFilter)地方的模塊。而且,useClass不是處理自定義提供者註冊的惟一方法。

  • 你能夠根據須要使用這種方法添加儘量多的過濾器,只需將每一個組件添加到provider數組。

捕獲全部

  • 爲了捕獲未處理的異常(無論異常的類型是什麼)。裝飾器@Catch()傳空就能夠了。
import { ExceptionFilter, Catch, ArgumentsHost, HttpException, HttpStatus } from '@nestjs/common';

@Catch()
export class AllExceptionsFilter implements ExceptionFilter {
  catch(exception: unknown, host: ArgumentsHost) {
    const ctx = host.switchToHttp();
    const response = ctx.getResponse();
    const request = ctx.getRequest();

    const status =
      exception instanceof HttpException
        ? exception.getStatus()
        : HttpStatus.INTERNAL_SERVER_ERROR;

    response.status(status).json({
      statusCode: status,
      timestamp: new Date().toISOString(),
      path: request.url,
    });
  }
}
複製代碼
  • 在上面的例子中,過濾器將捕獲全部拋出的異常,無論類型是什麼。

繼承

  • 一般,你將建立徹底自定義的異常過濾器,以知足應用程序需求。然而,在某些例子中,你可能但願簡單地擴展內置的默認全局異常過濾器,並基於某些因素覆蓋行爲。
  • 爲了將異常處理委託給基礎過濾器,你須要擴展BaseExceptionFilter並調用繼承的catch()方法。
import { Catch, ArgumentsHost } from '@nestjs/common';
import { BaseExceptionFilter } from '@nestjs/core';

@Catch()
export class AllExceptionsFilter extends BaseExceptionFilter {
  catch(exception: unknown, host: ArgumentsHost) {
    super.catch(exception, host);
  }
}
複製代碼

擴展`BaseExceptionFilter`的方法範圍和控制範圍的過濾器不該該用new實例化,讓框架自動實例化它們。

  • 上面的實現只是展現了該方法的結構。擴展異常過濾器的實現將包括你的定製的業務邏輯(例如,處理各類條件)。
  • 全局過濾器能夠擴展基礎過濾器,有兩種方法。
  • 第一種方法是在實例化自定義的全局過濾器時注入HttpServer參數
async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  const { httpAdapter } = app.get(HttpAdapterHost);
  app.useGlobalFilters(new AllExceptionsFilter(httpAdapter));

  await app.listen(3000);
}
bootstrap();
複製代碼
  • 第二種方法是使用APP_FILTER標識像這樣


管道

管道

  • 管道是使用裝飾器@Injectable()的類,管道應該實現PipeTransform接口

  • 管道由兩個典型的應用
    1. 轉換 —— 將輸入的數據轉換爲所需的數據輸出
    2. 校驗 —— 校驗輸入的數據,經過驗證則原封不動地傳遞,不然當數據不正確時拋出異常
  • 在這兩種狀況中,管道對控制器的路由處理函數正在處理的參數進行操做。Nest插入一個在函數調用前插入一個管道,而且管道接受函數指定的參數。任何轉換或驗證操做都在那時發生,而後使用任何(潛在的)轉換後的參數調用路由處理函數。

ps:管道運行在異常區域內。這意味這當管道拋出異常時,由異常層(全局異常過濾器和應用於當前環境的任何異常過濾器)處理。根據上面的說明,當異常在管道中拋出時,顯然不會執行控制器方法。


內置管道

  • Nest提供了三個開箱即用的管道:ValidationPipeParseIntPipeParseUUIDPipe。他們從@nestjs/common導出,爲了更好地理解它們是如何工做的,讓咱們從頭開始構建它們。
  • 讓咱們從ValidationPipe開始。起先咱們簡單地傳入一個值並馬上返回相同的值,就像一個確認函數。
import { PipeTransform, Injectable, ArgumentMetadata } from '@nestjs/common';

@Injectable()
export class ValidationPipe implements PipeTransform {
  transform(value: any, metadata: ArgumentMetadata) {
    return value;
  }
}
複製代碼

ps: PipeTransform<T, R>是一個泛型接口,其中T表示輸入值的類型,R表示transform()方法的返回類型。

  • 每一個管道提供了tranform()方法,由兩個參數:
    1. value
    2. metadata
  • value是當前處理的參數(在路由處理方法接收它以前),而metadata是它的元數據。meata對象具備這些屬性
export interface ArgumentMetadata {
  readonly type: 'body' | 'query' | 'param' | 'custom';
  readonly metatype?: Type<any>;
  readonly data?: string;
}
複製代碼
  • 這些屬性描述了當前正在處理的參數
type 代表了參數是@body()@Query()@Param()或者是一個自定義參數
metatype 爲參數提供了元數據,例如字符。注意:若是在路由處理函數的方法簽名中省略類型聲明,或者使用普通JavaScript,則該值爲undefined
data 傳入裝飾器的字符。例如:@Body('string'),傳空即爲undefined

TypeScript的接口(Interface)在轉換期間會消失。所以,若是方法參數的類型聲明爲接口而不是類,則metatype值將爲Object。


驗證的例子

  • 讓咱們仔細看看CatsControllercreate()方法。
@Post()
async create(@Body() createCatDto: CreateCatDto) {
  this.catsService.create(createCatDto);
}
複製代碼
  • 讓咱們關注createCatDtobody參數。它的類型是CreateCatDto
export class CreateCatDto {
  readonly name: string;
  readonly age: number;
  readonly breed: string;
}
複製代碼
  • 咱們但願確保對create方法來講任何傳入的請求都包含一個有效的請求體。所以,咱們必須驗證createCatDto對象的三個成員。咱們固然能夠在路由處理函數中作這件事情,但咱們將會打破單一責任規則(SRP)。另外一種方法是建立一個驗證器類並在其中委託任務,可是咱們必須在每一個方法的開頭使用這個驗證器。那建立一個驗證中間件怎麼樣?這多是一個好主意,可是這沒有辦法建立能夠跨整個應用程序使用的通用的中間件(由於中間件不知道執行環境,包括將要調用的處理函數及其任何參數)。事實證實,這種狀況很是適合於管道。因此咱們來作一個。

對象模式驗證

  • 有幾種可用的方法來驗證對象,一種常見的方法是使用基於模式的驗證。Joi庫容許你使用可讀的API以很是簡單的方式建立模式。讓咱們看看一個使用基於joi模式的管道。
  • 先安裝
$ npm install --save @hapi/joi
$ npm install --save-dev @types/hapi__joi
複製代碼
  • 在下面的代碼示例中,咱們建立了一個簡單的類,它接受模式做爲構造函數參數。而後咱們應用jo .validate()方法,該方法根據提供的模式驗證傳入參數。

  • 如上所述,驗證的管道要麼返回未更改的值,要麼拋出異常。

  • 在下一節中,你將看到咱們如何使用@UsePipes()裝飾器爲給定的控制器方法提供適當的模式。

import * as Joi from '@hapi/joi';
import { PipeTransform, Injectable, ArgumentMetadata, BadRequestException } from '@nestjs/common';

@Injectable()
export class JoiValidationPipe implements PipeTransform {
  constructor(private readonly schema: Object) {}

  transform(value: any, metadata: ArgumentMetadata) {
    const { error } = Joi.validate(value, this.schema);
    if (error) {
      throw new BadRequestException('Validation failed');
    }
    return value;
  }
}
複製代碼

綁定管道

  • 綁定管道(將它們綁定到適當的控制器或處理函數)很是簡單。咱們使用@UsePipes()裝飾器並建立一個管道實例,將一個Joi驗證模式傳遞給它。
@Post()
@UsePipes(new JoiValidationPipe(createCatSchema))
async create(@Body() createCatDto: CreateCatDto) {
  this.catsService.create(createCatDto);
}
複製代碼

類驗證器

本節中的技術須要TypeScript,若是應用程序是使用普通JavaScript編寫的,則不可用

  • 讓咱們看看驗證技術的另外一種可替代的實現。
  • Nest能夠很好地與類驗證器庫一塊兒工做。這個驚人的庫容許您使用基於裝飾器的驗證。基於裝飾器的驗證功能很是強大,尤爲是與Nest的管道功能結合使用時,由於咱們能夠訪問處理屬性的元類型。在開始以前,咱們須要安裝所需的軟件包
$ npm i --save class-validator class-transformer
複製代碼
  • 安裝了這些以後,咱們能夠向CreateCatDto類添加一些裝飾器。
import { IsString, IsInt } from 'class-validator';

export class CreateCatDto {
  @IsString()
  readonly name: string;

  @IsInt()
  readonly age: number;

  @IsString()
  readonly breed: string;
}
複製代碼

ps:更多關於類驗證器請閱讀這裏

  • 如今咱們能夠建立一個ValidationPipe
import { PipeTransform, Injectable, ArgumentMetadata, BadRequestException } from '@nestjs/common';
import { validate } from 'class-validator';
import { plainToClass } from 'class-transformer';

@Injectable()
export class ValidationPipe implements PipeTransform<any> {
  async transform(value: any, { metatype }: ArgumentMetadata) {
    if (!metatype || !this.toValidate(metatype)) {
      return value;
    }
    const object = plainToClass(metatype, value);
    const errors = await validate(object);
    if (errors.length > 0) {
      throw new BadRequestException('Validation failed');
    }
    return value;
  }

  private toValidate(metatype: Function): boolean {
    const types: Function[] = [String, Boolean, Number, Array, Object];
    return !types.includes(metatype);
  }
}
複製代碼

ps: 上面,咱們使用了類轉換器庫。它是由同一個做者編寫的,與類驗證器庫是同一個做者編寫的,所以,它們在一塊兒運行得很是好。

  • 讓咱們看看這段代碼。首先,注意transform()函數是異步的。這是可能的,由於Nest同時支持同步和異步管道。咱們這樣作是由於一些類驗證器驗證能夠是異步的(利用promise)。
  • 接下來注意,咱們使用解構來提取metatype字段(僅從ArgumentMetadata中提取此成員)到metatype參數中。這只是獲取完整ArgumentMetadata的簡寫,而後使用附加語句來分配元類型變量。
  • 接下來,注意輔助函數toValidate()。噹噹前處理的參數是原生JavaScript類型時,它負責繞過驗證步驟(這些參數不能附加模式,所以沒有理由在驗證步驟中運行它們)。
  • 接下來,咱們使用類轉換器函數plain toclass()將普通JavaScript參數對象轉換爲類型化對象,以便應用驗證。從網絡請求反序列化傳入的主體時,會沒有任何類型的信息。類驗證器須要使用前面爲DTO定義的驗證裝飾器,所以須要執行此轉換。
  • 最後,如前所述,因爲這是一個驗證管道,它要麼返回未更改的值,要麼拋出異常。
  • 最後一步是綁定ValidationPipe。管道相似於異常過濾器,能夠是方法範圍的、控制範圍的或全局範圍的。此外,管道能夠是參數做用域的。在下面的示例中,咱們將直接將管道實例綁定到路由參數的@Body()裝飾器。
@Post()
async create(
  @Body(new ValidationPipe()) createCatDto: CreateCatDto,
) {
  this.catsService.create(createCatDto);
}
複製代碼
  • 當驗證邏輯只涉及一個指定參數時,參數做用域的管道很是有用。

  • 或者,要用方法級別設置管道,可使用@UsePipes()裝飾器。

@Post()
@UsePipes(new ValidationPipe())
async create(@Body() createCatDto: CreateCatDto) {
  this.catsService.create(createCatDto);
}
複製代碼
  • @UesPipe()裝飾器由@nestjs/common導出
  • 在上面的示例中,當即就地建立了ValidationPipe實例。或者,傳遞類(而不是實例),從而將實例化留給框架,並啓用依賴項注入。
@Post()
@UsePipes(ValidationPipe)
async create(@Body() createCatDto: CreateCatDto) {
  this.catsService.create(createCatDto);
}
複製代碼
  • 因爲ValidationPipe被建立爲儘量通用,因此讓咱們將它設置爲全局範圍的管道,應用於整個應用程序中的每一個路由處理程序。
async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useGlobalPipes(new ValidationPipe());
  await app.listen(3000);
}
bootstrap();
複製代碼

ps: 在混合應用程序的狀況下,useGlobalPipes()方法不會爲網關和微服務設置管道。對於「標準」(非混合)微服務應用程序,useGlobalPipes()在全局安裝管道。

  • 全局管道用於整個應用程序,用於每一個控制器和每一個路由處理程序。在依賴項注入方面,從任何模塊外部註冊的全局管道(如上例中使用useGlobalPipes())不能注入依賴項,由於這是在任何模塊的環境以外完成的。爲了解決這個問題,您可使用如下構造從任何模塊直接設置全局管道
import { Module } from '@nestjs/common';
import { APP_PIPE } from '@nestjs/core';

@Module({
  providers: [
    {
      provide: APP_PIPE,
      useClass: ValidationPipe,
    },
  ],
})
export class AppModule {}
複製代碼

ps: 當使用此方法爲管道執行依賴項注入時,請注意,不管使用此構造的模塊是什麼,管道實際上都是全局的。這應該在哪裏進行?選擇定義管道的模塊(上面示例中的ValidationPipe)。並且,useClass不是處理自定義提供者註冊的惟一方法。瞭解更多


轉換例子

  • 驗證不是管道的惟一的案例。在本章的開頭,咱們提到管道還能夠將輸入數據轉換爲所需的輸出。這是可能的,由於transform函數返回的值徹底覆蓋了參數的前一個值。何時有用?考慮到有時從客戶端傳遞的數據須要進行一些更改(例如將字符串轉換爲整數),而後才能由route處理方法正確處理。此外,一些必需的數據字段可能會丟失,咱們但願應用默認值,Transformer pipes能夠經過在客戶端請求和請求處理程序之間插入一個處理函數來執行這些功能。
  • 這是一個ParseIntPipe,它負責將字符串解析爲整數值。
import { PipeTransform, Injectable, ArgumentMetadata, BadRequestException } from '@nestjs/common';

@Injectable()
export class ParseIntPipe implements PipeTransform<string, number> {
  transform(value: string, metadata: ArgumentMetadata): number {
    const val = parseInt(value, 10);
    if (isNaN(val)) {
      throw new BadRequestException('Validation failed');
    }
    return val;
  }
}
複製代碼
  • 咱們能夠簡單地將這個管道綁定到所選的參數,以下所示
@Get(':id')
async findOne(@Param('id', new ParseIntPipe()) id) {
  return await this.catsService.findOne(id);
}
複製代碼
  • 若是您願意,可使用ParseUUIDPipe,它負責解析字符串並驗證是否爲UUID。
@Get(':id')
async findOne(@Param('id', new ParseUUIDPipe()) id) {
  return await this.catsService.findOne(id);
}
複製代碼

ps: 當使用ParseUUIDPipe()時,您在版本三、4或5中解析UUID,若是您只須要特定版本的UUID,那麼您能夠在管道選項中傳遞一個版本。

  • 有了這一點,ParseIntPipeParseUUIDPipe將在請求到達相應的處理函數以前執行,確保它老是會收到id參數的整數或uuid(根據使用的管道)。另外一種有用的狀況是經過id從數據庫中選擇一個現有的用戶實體
@Get(':id')
findOne(@Param('id', UserByIdPipe) userEntity: UserEntity) {
  return userEntity;
}
複製代碼
  • 請注意,與全部其餘轉換管道同樣,它接收一個輸入值(一個id)並返回一個輸出值(一個UserEntity對象)。經過將樣板代碼從處理函數抽象到公共管道中,這可使您的代碼更具聲明性和乾爽性。

內置的驗證管道

  • 幸運的是,您沒必要本身構建這些管道,由於ValidationPipeParseIntPipe是由Nest開箱即用提供的。(請記住ValidationPipe須要同時安裝類驗證器和類轉換器包)。
  • 內置的ValidationPipe提供了比咱們在本章中構建的示例更多的選項,爲了說明管道的基本機制,本章保持了基本的驗證。你能夠在這裏找到不少例子。
  • 其中一個選擇就是轉換。回想一下前面關於反序列化體對象是普通JavaScript對象的討論(即,沒有咱們的DTO類型)。到目前爲止,咱們已經使用管道驗證了負載。你可能還記得,在這個過程當中,咱們使用class-transform將普通對象臨時轉換爲類型化對象,以便進行驗證。內置的ValidationPipe還能夠選擇性地返回這個轉換後的對象。咱們經過向管道傳遞配置對象來啓用此行爲。對於這個選項,傳遞一個帶有字段轉換和值true的配置對象
@Post()
@UsePipes(new ValidationPipe({ transform: true }))
async create(@Body() createCatDto: CreateCatDto) {
  this.catsService.create(createCatDto);
}
複製代碼

ps: ValidationPipe@nestjs/common package導出。

  • 由於這個管道基於類驗證器和類轉換器庫,因此有許多額外的選項可用。與上面的transform選項同樣,您能夠經過傳遞給管道的配置對象來配置這些設置。如下是內置選項
export interface ValidationPipeOptions extends ValidatorOptions {
  transform?: boolean;
  disableErrorMessages?: boolean;
  exceptionFactory?: (errors: ValidationError[]) => any;
}
複製代碼
  • 除此以外,全部類驗證器選項(繼承自ValidatorOptions接口)都是可用的
配置 類型 描述
skipMissingProperties boolean 若是設置爲true,驗證器將跳過驗證對象中缺乏的全部屬性的驗證。
whitelist boolean 若是設置爲true,validator將剝離不使用任何驗證修飾符的任何屬性的已驗證(返回)對象。
forbidNonWhitelisted boolean 若是設置爲true,則拋出異常而不是剝離非白名單屬性驗證器。
forbidUnknownValues boolean 若是設置爲true,驗證未知對象的嘗試將當即失敗。
disableErrorMessages boolean 若是設置爲true,驗證錯誤將不會返回給客戶端。
exceptionFactory Function 獲取驗證錯誤數組,並返回要拋出的異常對象。
groups string[] 在對象驗證期間使用的數組。
dismissDefaultMessages boolean 若是設置爲true,驗證將不使用默認信息。若是沒有明確設置錯誤信息,則該信息始終是undefined
validationError.target boolean 表示目標是否應在ValidationError中暴露
validationError.value boolean 表示驗證的值是否應在ValidationError中暴露

ps: 在其存儲庫中查找有關類驗證器包的更多信息



守衛

守衛

  • 守衛是一個使用裝飾器@Injectable()的類,守衛應該繼承CanActivate接口

  • 守衛只有一個責任。他們決定了一個請求是否要交予路由函數處理,取決於在運行時出現的某些狀況(例如權限、角色、ACL等)。這一般叫作受權。受權(一般和他的兄弟——身份驗證——一塊兒合做)在傳統的Express當中一向是由中間件來處理。中間件對於身份驗證是一個好的選擇,由於像令牌驗證或是在request對象上增長屬性和一個特定的路由環境(還有他的元數據)並無很大的關聯。
  • 可是中間件自己是愚蠢的,他並不知道調用next()方法後哪個路由處理函數將被執行。另外一方面,守衛能夠訪問ExecutionContext的實例,這樣就能準確地知道下一個執行的函數。它們的設計很像異常過濾器、管道和攔截器,讓你在request/response循環中在正確的位置插入處理邏輯,並用聲明的方式來實現。

ps:守衛會在每一箇中間件以後執行,可是會在攔截器和管道以前。


受權守衛

  • 正如所說的,受權是守衛的一個很好的案例,由於只有當調用者(一般是通過身份驗證的特定用戶)具備足夠的權限時,才應該使用特定的路由。
  • 咱們如今要構建的AuthGuard假設有一個通過身份驗證的用戶(所以,請求頭附加了一個令牌)。它將提取和驗證令牌,並使用提取的信息來肯定請求是否能夠繼續。
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Observable } from 'rxjs';

@Injectable()
export class AuthGuard implements CanActivate {
  canActivate(
    context: ExecutionContext,
  ): boolean | Promise<boolean> | Observable<boolean> {
    const request = context.switchToHttp().getRequest();
    return validateRequest(request);
  }
}
複製代碼
  • validateRequest()函數中的邏輯能夠根據須要簡單或複雜。本例的主要目的是展現守衛是如何適應request/response循環
  • 每一個守衛必須實現canActivate()函數。函數應該返回一個布爾值,表示了當前請求是否容許經過。他能夠同步或異步地返回響應(經過Promise或者Observable)。Nest根據返回值來控制下一個動做:
    • 若是返回true,請求將被處理
    • 若是返回false,Nest將拒絕該請求
  • canActivate()函數接受一個參數,ExecutionContext的實例。ExecutionContext繼承自ArgumentsHost。咱們在以前異常過濾器的章節中看到過ArgumentsHost。在那裏,它是傳遞給原始處理函數的參數的包裝器,而且包含了基於應用類型的不一樣的參數數組。有關此主題的更多信息,請參閱異常過濾器。

執行環境

  • 經過擴展ArgumentsHostExecutionContext提供了關於當前執行過程的更多細節。
export interface ExecutionContext extends ArgumentsHost {
  getClass<T = any>(): Type<T>;
  getHandler(): Function;
}
複製代碼
  • getHandler()方法返回對將要調用的處理函數的引用。getClass()方法返回這個特定處理函數所屬的控制器類的類型。例如,若是當前處理的請求是一個POST請求,目標是CatsController上的create()方法,getHandler()將返回對create()方法的引用,getClass()將返回一個CatsController類型(而不是實例)。

基於角色的身份驗證

  • 讓咱們構建一個功能更強的守衛,它只容許具備特定角色的用戶訪問。咱們將從一個基本的守衛模板開始,並在接下來的部分中構建它。目前,它容許全部請求繼續進行
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Observable } from 'rxjs';

@Injectable()
export class RolesGuard implements CanActivate {
  canActivate(
    context: ExecutionContext,
  ): boolean | Promise<boolean> | Observable<boolean> {
    return true;
  }
}
複製代碼

綁定守衛

  • 與管道和異常過濾器同樣,守衛能夠是控制器範圍的、方法範圍的或全局範圍的。下面,咱們使用@ useguard()裝飾器設置了一個控制器範圍的守衛。這個裝飾器可使用單個參數,也可使用逗號分隔的參數列表。‘
@Controller('cats')
@UseGuards(RolesGuard)
export class CatsController {}
複製代碼

ps: UseGuards()裝飾器由@nestjs/common導出

  • 上面,咱們傳遞了RolesGuard類型(而不是實例),將實例化的責任留給框架並啓用依賴項注入。與管道和異常過濾器同樣,咱們也能夠就地傳遞一個實例。
@Controller('cats')
@UseGuards(new RolesGuard())
export class CatsController {}
複製代碼
  • 上面的構造將守衛附加到此控制器聲明的每一個處理程序。若是咱們但願守衛只應用於一個方法,咱們將在方法級別應用@useguard()裝飾器。
  • 要設置全局警衛,請使用Nest應用程序實例的useglobalguard()方法
const app = await NestFactory.create(AppModule);
app.useGlobalGuards(new RolesGuard());
複製代碼

ps: 在混合應用程序的狀況下,useglobalguard()方法不會爲網關和微服務設置保護。對於「標準」(非混合)微服務應用程序,useglobalguard()確實在全球安裝了這些警衛。

  • 全局守衛用於整個應用程序,用於每一個控制器和每一個路由處理函數。在依賴項注入方面,從任何模塊外部註冊的全局警衛(如上例中使用useglobalguard())不能注入依賴項,由於這是在任何模塊的環境以外完成的。爲了解決這個問題,您可使用如下構造從任何模塊直接設置一個守衛
import { Module } from '@nestjs/common';
import { APP_GUARD } from '@nestjs/core';

@Module({
  providers: [
    {
      provide: APP_GUARD,
      useClass: RolesGuard,
    },
  ],
})
export class AppModule {}
複製代碼

ps: 當使用此方法爲守衛執行依賴項注入時,請注意,不管使用此構造的模塊是什麼,守衛實際上都是全局的。這應該在哪裏進行?選擇定義守衛的模塊(在上面的例子中是RolesGuard)。並且,useClass不是處理自定義提供者註冊的惟一方法。瞭解更多


映射

  • 咱們的RolesGuard工做正常,但還不是很智能。咱們尚未利用最重要的守衛特性——執行環境。它還不知道角色,或者每一個處理程序容許哪些角色。例如,CatsController能夠爲不一樣的路由提供不一樣的權限方案。其中一些可能只對管理員用戶可用,而另外一些則能夠對全部人開放。咱們如何以靈活和可重用的方式將角色匹配到路由?
  • 這就是定製元數據發揮做用的地方。Nest提供了經過@SetMetadata()裝飾器將定製元數據附加到路由處理程序的能力。這個元數據提供了咱們丟失的角色數據,智能守衛須要這些數據來作出決策。讓咱們看看如何使用@SetMetadata()
@Post()
@SetMetadata('roles', ['admin'])
async create(@Body() createCatDto: CreateCatDto) {
  this.catsService.create(createCatDto);
}
複製代碼

ps: 裝飾器@SetMetadata()nestjs/common包導出

  • 經過上述構造,咱們將角色元數據(角色是一個鍵,而['admin']是一個特定值)附加到create()方法。雖然這樣作是有效的,可是在路由中直接使用@SetMetadata()不是很好的作法。相反,建立您本身的裝飾器,以下所示
import { SetMetadata } from '@nestjs/common';

export const Roles = (...roles: string[]) => SetMetadata('roles', roles);
複製代碼
  • 這種方法更簡潔、可讀性更強,並且是強類型的。如今咱們有了一個自定義的@Roles()裝飾器,咱們可使用它來裝飾create()方法。
@Post()
@Roles('admin')
async create(@Body() createCatDto: CreateCatDto) {
  this.catsService.create(createCatDto);
}
複製代碼

融會貫通

  • 如今讓咱們回去把它和咱們的RolesGuard綁在一塊兒。目前,它只是在全部狀況下返回true,容許每一個請求繼續。咱們但願將分配給當前用戶的角色與正在處理的當前路由所需的實際角色進行比較,從而使返回值具備條件。爲了訪問路由的角色(自定義元數據),咱們將使用Reflector輔助類,它由框架提供,並從@nestjs/common公開。
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Observable } from 'rxjs';
import { Reflector } from '@nestjs/core';

@Injectable()
export class RolesGuard implements CanActivate {
  constructor(private readonly reflector: Reflector) {}

  canActivate(context: ExecutionContext): boolean {
    const roles = this.reflector.get<string[]>('roles', context.getHandler());
    if (!roles) {
      return true;
    }
    const request = context.switchToHttp().getRequest();
    const user = request.user;
    const hasRole = () => user.roles.some((role) => roles.includes(role));
    return user && user.roles && hasRole();
  }
}
複製代碼

ps: 在node.js中,一般將受權用戶附加到請求對象。所以,在上面的示例代碼中,咱們假設該請求。user包含用戶實例和容許的角色。在您的應用程序中,您可能會在自定義身份驗證守衛(或中間件)中建立關聯。

  • Reflector類容許咱們經過指定的鍵輕鬆地訪問元數據(在本例中,鍵是「roles」;返回到roles.decorator.ts文件和在那裏發出的SetMetadata()調用。在上面的示例中,爲了提取當前處理的請求方法的元數據,咱們傳遞了context.getHandler()。記住,getHandler()提供了對路由處理函數的引用。
  • 咱們能夠經過提取控制器元數據並使用該元數據來肯定當前用戶角色,從而使這個守衛更加通用。爲了提取控制器元數據,咱們傳遞context.getClass()而不是context.getHandler()
const roles = this.reflector.get<string[]>('roles', context.getClass());
複製代碼
  • 當權限不足的用戶請求端點時,Nest自動返回如下響應
{
  "statusCode": 403,
  "message": "Forbidden resource"
}
複製代碼

注意,在後臺,當一個守衛返回false時,框架拋出一個ForbiddenException。若是但願返回不一樣的錯誤響應,應該拋出本身的特定異常。例如

throw new UnauthorizedException();
複製代碼
  • 由守衛引起的任何異常都將由異常層(全局異常過濾器和應用於當前環境的任何異常過濾器)處理。


攔截器

攔截器

  • 攔截器是使用裝飾器@injectable的類,攔截器應該實現NestInterceptor接口

  • 攔截器具備一組有用的功能,這些功能受到面向方面編程(AOP)技術的啓發。他們使之成爲可能:
    1. 在方法執行前/後綁定額外的邏輯
    2. 轉換函數返回的結果
    3. 轉換函數拋出的異常
    4. 擴展基礎函數的行爲
    5. 根據特定條件徹底重載函數

基礎

  • 每一個攔截器實現了intercept()方法,接受兩個參數。第一個是ExecutionContext的實例(和守衛同樣)。ExecutionContext繼承自ArgumentsHost。咱們在前面的異常過濾器一章中看到了ArgumentsHost。在那裏,咱們看到它是傳遞給原始處理函數的參數的包裝器,並根據應用程序的類型包含不一樣的參數數組。

Execution context 執行環境

  • 經過擴展ArgumentsHost, ExecutionContext提供了關於當前執行過程的更多細節。這是它的樣子
export interface ExecutionContext extends ArgumentsHost {
  getClass<T = any>(): Type<T>;
  getHandler(): Function;
}
複製代碼
  • getHandler()方法返回對將要調用的路由處理程序的引用,getClass()方法返回這個特定處理程序所屬的控制器類的類型。例如,若是當前處理的請求是一個POST請求,目標是CatsController上的create()方法,getHandler()將返回對create()方法的引用,getClass()將返回一個CatsControllertype(而不是實例)。

調用處理函數

  • 第二個參數是CallHandlerCallHandler接口實現handle()方法,你可使用該方法在攔截器中的某個點調用路由處理函數。若是在攔截器intercept()方法的實現中不調用handle()方法,則根本不會執行路由處理函數。

部分攔截

  • 第一個用例是使用攔截器來記錄用戶交互(例如,存儲用戶調用、異步調度事件或計算時間戳)。咱們在下面展現了一個簡單的日誌攔截器
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators';

@Injectable()
export class LoggingInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    console.log('Before...');

    const now = Date.now();
    return next
      .handle()
      .pipe(
        tap(() => console.log(`After... ${Date.now() - now}ms`)),
      );
  }
}
複製代碼

ps: NestInterceptor<T, R>是一個泛型接口,其中T表示一個Observable<T>(支持響應流)的類型,RObservable<R>封裝的值的類型。

ps: 攔截器,如控制器、提供者、守衛等,能夠經過它們的構造函數注入依賴項。

  • 因爲handle()返回一個RxJs的Observable,因此咱們可使用多種操做符來操做流。在上面的例子中,咱們使用了tap()操做符,在observable流優雅或異常終止時調用匿名日誌函數,但不會在其餘方面干擾響應循環。

綁定攔截器

  • 爲了設置攔截器,咱們使用從@nestjs/common導入的@UseInterceptors()裝飾器,與管道和守衛同樣,攔截器能夠是控制器範圍的、方法範圍的或全局範圍的。
@UseInterceptors(LoggingInterceptor)
export class CatsController {}
複製代碼
  • 使用上述構造,CatsController中定義的每一個路由處理函數都將使用LoggingInterceptor。當有人調用GET /cats端點時,您將在標準輸出中看到如下輸出
Before...
After... 1ms
複製代碼
  • 注意,咱們傳遞了LoggingInterceptor類型(而不是實例),將實例化的責任留給框架並啓用依賴項注入。與管道、守衛和異常過濾器同樣,咱們也能夠傳遞一個就地實例。
@UseInterceptors(new LoggingInterceptor())
export class CatsController {}
複製代碼
  • 如上所述,上面的構造將攔截器附加到此控制器聲明的每一個處理函數。若是咱們想將攔截器的範圍限制爲一個方法,咱們只需在方法級別應用裝飾器。

  • 爲了設置全局攔截器,咱們使用了Nest應用程序實例的useGlobalInterceptors()方法

const app = await NestFactory.create(AppModule);
app.useGlobalInterceptors(new LoggingInterceptor());
複製代碼
  • 全局攔截器用於整個應用程序,用於每一個控制器和每一個路由處理函數 。在依賴項注入方面,從任何模塊外部註冊的全局攔截器(使用useGlobalInterceptors(),如上例所示)不能注入依賴項,由於這是在任何模塊的環境以外完成的。爲了解決這個問題,您可使用如下構造直接從任何模塊設置攔截器
import { Module } from '@nestjs/common';
import { APP_INTERCEPTOR } from '@nestjs/core';

@Module({
  providers: [
    {
      provide: APP_INTERCEPTOR,
      useClass: LoggingInterceptor,
    },
  ],
})
export class AppModule {}
複製代碼

ps: 當使用此方法爲攔截器執行依賴項注入時,請注意,不管使用此構造的模塊是什麼,攔截器實際上都是全局的。這應該在哪裏進行?選擇定義攔截器(上例中的LoggingInterceptor)的模塊。並且,useClass不是處理自定義提供者註冊的惟一方法。瞭解更多


響應映射

  • 咱們已經知道handle()返回一個Obseverable對象。流包含路由處理函數返回的值,所以咱們可使用RxJS的map()操做符輕鬆地對其進行修改。

響應映射特性與指定庫library-specific的響應策略不兼容(禁止直接使用`@Res()`對象)。

  • 讓咱們建立TransformInterceptor,它將以一種簡單的方式修改每一個響應,以演示流程。它將使用RxJS的map()操做符將響應對象分配給新建立對象的data屬性,並將新對象返回給客戶端。
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';

export interface Response<T> {
  data: T;
}

@Injectable()
export class TransformInterceptor<T> implements NestInterceptor<T, Response<T>> {
  intercept(context: ExecutionContext, next: CallHandler): Observable<Response<T>> {
    return next.handle().pipe(map(data => ({ data })));
  }
}
複製代碼

ps: 嵌套攔截器同時使用同步和異步intercept()方法。若是須要,您能夠簡單地將方法切換到async

  • 使用上述構造,當有人調用GET /cats端點時,響應將以下所示(假設路由處理程序返回一個空數組[])
{
  "data": []
}
複製代碼
  • 攔截器在爲跨整個應用程序發生的需求建立可重用的解決方案方面具備很大的價值。例如,假設咱們須要將每次出現的空值轉換爲空字符串。咱們可使用一行代碼來實現這一點,並全局綁定攔截器,這樣每一個註冊的處理程序都會自動使用它。
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';

@Injectable()
export class ExcludeNullInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    return next
      .handle()
      .pipe(map(value => value === null ? '' : value ));
  }
}
複製代碼

異常映射

  • 另外一個有趣的用例是利用RxJS的catchError()操做符覆蓋拋出的異常
import {
  Injectable,
  NestInterceptor,
  ExecutionContext,
  BadGatewayException,
  CallHandler,
} from '@nestjs/common';
import { Observable, throwError } from 'rxjs';
import { catchError } from 'rxjs/operators';

@Injectable()
export class ErrorsInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    return next
      .handle()
      .pipe(
        catchError(err => throwError(new BadGatewayException())),
      );
  }
}
複製代碼

流重載

  • 咱們有時可能但願徹底避免調用處理函數,而是返回一個不一樣的值,這有幾個緣由。一個明顯的例子是實現緩存以加速響應時間。讓咱們來看一個簡單的緩存攔截器,它從緩存返回響應。在實際的示例中,咱們但願考慮其餘因素,如TTL、緩存失效、緩存大小等,但這超出了本文的討論範圍。這裏咱們將提供一個基本示例來演示主要概念。
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
import { Observable, of } from 'rxjs';

@Injectable()
export class CacheInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    const isCached = true;
    if (isCached) {
      return of([]);
    }
    return next.handle();
  }
}
複製代碼
  • 咱們的緩存攔截器有一個硬編碼的isCached變量和一個硬編碼的響應數組。須要注意的關鍵點是,咱們在這裏返回一個由RxJSof()操做符建立的新流,所以根本不會調用路由處理程序。當有人調用使用CacheInterceptor的端點時,響應(硬編碼的空數組)將當即返回。爲了建立一個通用的解決方案,您能夠利用Reflector並建立一個自定義裝飾器。Reflector在守衛一章中有很好的描述。

更多的操做

  • 使用RxJS操做符操做流的可能性爲咱們提供了許多功能。讓咱們考慮另外一個常見的用例。假設您但願處理路由請求上的超時。當端點在一段時間以後沒有返回任何內容時,您但願使用錯誤響應終止。下面的構造實現了這一點
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
import { Observable } from 'rxjs';
import { timeout } from 'rxjs/operators';

@Injectable()
export class TimeoutInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    return next.handle().pipe(timeout(5000))
  }
}
複製代碼
  • 5秒後,請求進程將被取消。


自定義裝飾器

自定義路由裝飾器

  • Nest是圍繞一種名爲decorator的語言特性構建的。裝飾器在許多經常使用的編程語言中都是一個衆所周知的概念,但在JavaScript世界中,它們仍然相對較新。爲了更好地理解decorator是如何工做的,咱們建議閱讀本文。這裏有一個簡單的定義

An ES2016 decorator is an expression which returns a function and can take a target, name and property descriptor as arguments. You apply it by prefixing the decorator with an @ character and placing this at the very top of what you are trying to decorate. Decorators can be defined for either a class or a property.


參數裝飾器

  • Nest提供了一組有用的參數裝飾器,能夠與HTTP路由處理程序一塊兒使用。下面是所提供的裝飾器和它們所表示的普通Express(或Fastify)對象的列表
@Request() req
@Response() res*
@Next() next
@Session() req.session
@Param(key?: string) req.params / req.params[key]
@Body(key?: string) req.body / req.body[key]
@Query(key?: string) req.query / req.query[key]
@Headers(name?: string) req.headers / req.headers[name]
  • 此外,您還能夠建立本身的自定義裝飾器。爲何這個有用?
  • 在node.js世界中,一般將屬性附加到請求對象。而後在每一個路由處理程序中手動提取它們,使用以下代碼
const user = req.user;
複製代碼
  • 爲了使代碼更具可讀性和透明性,您能夠建立一個@User()裝飾器,並在全部控制器之間重用它。
import { createParamDecorator } from '@nestjs/common';

export const User = createParamDecorator((data, req) => {
  return req.user;
});
複製代碼
  • 而後,您能夠簡單地在任何適合須要的地方使用它。
sync findOne(@User() user: UserEntity) {
  console.log(user);
}
複製代碼

傳入data參數

  • 當裝飾器的行爲取決於某些條件時,可使用data參數將參數傳遞給裝飾器的工廠函數。一個用例是自定義裝飾器,它按鍵從請求對象中提取屬性。例如,假設咱們的身份驗證層驗證請求並將用戶實體附加到請求對象。通過身份驗證的請求的用戶實體可能以下所示
{
  "id": 101,
  "firstName": "Alan",
  "lastName": "Turing",
  "email": "alan@email.com",
  "roles": ["admin"]
}
複製代碼
  • 讓咱們定義一個裝飾器,它接受屬性名做爲鍵,若是屬性名存在,則返回關聯的值(若是不存在,則返回未定義的值,或者若是用戶對象沒有建立,則返回未定義的值)。
import { createParamDecorator } from '@nestjs/common';

export const User = createParamDecorator((data: string, req) => {
  return data ? req.user && req.user[data] : req.user;
});
複製代碼
  • 下面是如何經過控制器中的@User()裝飾器訪問特定的屬性
@Get()
async findOne(@User('firstName') firstName: string) {
  console.log(`Hello ${firstName}`);
}
複製代碼
  • 您可使用具備不一樣鍵的相同裝飾器來訪問不一樣的屬性。若是用戶對象是深度或複雜的,這可使請求處理程序實現更容易、更可讀。

和管道一塊兒用

  • Nest以與內置參數裝飾器(@Body()@Param()@Query()相同的方式處理定製的參數裝飾器。這意味着管道也將爲自定義帶註釋的參數執行(在咱們的示例中,是用戶參數)。此外,您還能夠將管道直接應用於自定義裝飾器
@Get()
async findOne(@User(new ValidationPipe()) user: UserEntity) {
  console.log(user);
}
複製代碼

overview結束




關於本文

  • 本文做爲學習nest框架的一篇筆記,對英文官方文檔進行了直譯、意譯和理解。
  • 翻譯多有變扭之處,是比較大量的工做,後續應該持續會糾正。
  • 用做記錄本身曾經學習、思考過的問題的一種筆記。
  • 用做前端技術交流分享。
  • 閱讀本文時歡迎隨時質疑本文的準確性,將錯誤的地方告訴我。本人會積極修改,避免文章對讀者的誤導。

關於我

  • 是一隻有夢想的肥柴。
  • 以爲算法、數據結構、函數式編程、js底層原理等十分有趣的小前端。
  • 志同道合的朋友請關注我,一塊兒交流技術,在前端之路上共同成長。
  • 如對本人有任何意見建議儘管告訴我哦~ 初爲肥柴,請多多關照~
  • 前端路漫漫,技術學不完。今天也是美(diao)好(fa)的一天( 跪了...orz
相關文章
相關標籤/搜索