Nest入門教程 - 初識控制器

Nest入門教程 - 初識控制器

 

控制器

 

 

控制器負責處理傳入的 請求 和向客戶端返回 響應 。

 

控制器的目的是接收應用的特定請求。路由機制控制哪個控制器接收哪些請求。通常,每個控制器有多個路由,不同的路由可以執行不同的操作。

爲了創建一個基本的控制器,我們必須使用裝飾器。裝飾器將類與所需的元數據關聯,並使 Nest 能夠創建路由映射(將請求綁定到相應的控制器)。

 

路由

在下面的例子中,我們使用了定義基本控制器所需的 @Controller('cats') 裝飾器。我們將可選前綴設置爲 cats。使用前綴可以避免在所有路由共享通用前綴時出現衝突的情況。我們將使用 @Controller() 裝飾器,這是定義基本控制器所必需的。我們將指定一個路徑前綴(可選) cats。在 @Controller() 裝飾器中使用路徑前綴,它允許我們輕鬆對一組相關路由進行分組,並減少重複代碼。例如,我們可以選擇管理該路由下的客戶實體的交互的這部分進行分組 /customers ,這樣, 我們可以在 @Controller() 裝飾器中指定路徑前綴, 這樣我們就不必爲文件中的每個路由重新定義前綴。

cats.controller.ts

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

@Controller('cats')
export class CatsController {
  @Get()
  findAll(): string {
    return 'This action returns all cats';
  }
}Copy to clipboardErrorCopied

要使用 CLI 創建控制器,只需執行 $ nest g controller cats 命令。

findAll()方法之前的 @Get() HTTP 請求方法裝飾器告訴 Nest 爲HTTP請求的特定端點創建處理程序。端點對應於 HTTP 請求方法(在本例中爲 GET)和路由。什麼是路由 ? 處理程序的路由是通過連接爲控制器聲明的(可選)前綴和請求裝飾器中指定的任何路由來確定的。由於我們已經爲每個 routecats) 聲明瞭一個前綴,並且沒有在裝飾器中添加任何路由信息,因此 Nest會將 GET /cats 請求映射到此處理程序。如上所述,該路由包括可選的控制器路由前綴和請求方法裝飾器中聲明的任何路由。例如,customers 與裝飾器組合的路由前綴 @Get('profile') 會爲請求生成路由映射 GET /customers/profile

在上面的示例中,當對此端點發出 GET 請求時,Nest 會將請求路由到我們的用戶定義 findAll() 方法。請注意,我們在此處選擇的函數名稱完全是任意的。我們顯然必須聲明一個綁定路由的函數,但 Nest 不會對所選的函數名稱附加任何意義。

此函數將返回 200 狀態代碼和相關的響應,在這種情況下只返回了一個字符串。爲什麼會這樣? 我們將首先介紹 Nest 使用兩種不同的操作響應選項的概念:

   
標準(推薦) 使用這個內置方法,當請求處理程序返回一個 JavaScript 對象或數組時,它將自動序列化爲 JSON。但是,當它返回一個 JavaScript 基本類型(例如string、number、boolean)時,Nest 將只發送值,而不嘗試序列化它。這使響應處理變得簡單:只需要返回值,其餘的由 Nest負責。
  此外,響應的狀態碼默認情況下始終爲 200,但使用 201 的 POST請求除外。我們可以通過在處理程序級別添加 @HttpCode(...) 裝飾器來輕鬆更改此行爲 (狀態代碼
類庫特有的 我們可以在函數簽名通過 @Res() 注入類庫特定的 響應對象(例如,Express),使用此函數,您具有使用該對象的響應處理函數。例如,使用 Express,您可以使用類似代碼構建響應 response.status(200).send()

注意! 禁止同時使用這兩種方法。 Nest 檢測處理程序是否正在使用 @Res()或 @Next(),如果兩個方法都用了的話, 那麼在這裏的標準方式就是自動禁用此路由, 你將不會得到你想要的結果。

 

Request

許多端點需要訪問客戶端的請求細節。實際上,Nest 正使用類庫特有(默認是express)的請求對象。因此,我們可以強制 Nest 使用 @Req() 裝飾器將請求對象注入處理程序。

cats.controller.ts

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';
  }
}Copy to clipboardErrorCopied

爲了在 express 中使用 Typescript (如 request: Request 上面的參數示例所示),請安裝 @types/express 。

Request 對象表示 HTTP 請求,並具有 Request 查詢字符串,參數,HTTP 標頭 和 正文的屬性(在這裏閱讀更多),但在大多數情況下, 不必手動獲取它們。 我們可以使用專用的裝飾器,比如開箱即用的 @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]

爲了與底層 HTTP平臺(如 Express和 Fastify)之間的類型兼容,Nest 提供了 @Res()和 @Response() 裝飾器。@Res()只是 @Response()的別名。兩者都直接公開底層響應對象接口。在使用它們時,您還應該導入底層庫的類型(例如:@types/express)以充分利用它們。注意,在方法處理程序中注入 @Res()或 @Response() 時,將 Nest置於該處理程序的特定於庫的模式中,並負責管理響應。這樣做時,必須通過調用響應對象(例如,res.json(…)或 res.send(…))發出某種響應,否則HTTP服務器將掛起。

想要了解如何創建自定義的裝飾器,閱讀這一章

 

資源

我們已經創建了一個端點來獲取數據(GET 路由)。 我們通常還希望提供一個創建新記錄的端點。爲此,讓我們創建 POST 處理程序:

cats.controller.ts

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';
  }
}Copy to clipboardErrorCopied

就這麼簡單。Nest以相同的方式提供其餘的端點裝飾器- @Put() 、 @Delete()、 @Patch()、 @Options()、 @Head()和 @All()。這些表示各自的 HTTP請求方法。

 

路由通配符

路由同樣支持模式匹配。例如,星號被用作通配符,將匹配任何字符組合。

@Get('ab*cd')
findAll() {
  return 'This route uses a wildcard';
}Copy to clipboardErrorCopied

以上路由地址將匹配 abcd 、 ab_cd 、 abecd 等。字符 ? 、 + 、 * 以及 () 是它們的正則表達式對應項的子集。連字符 (-) 和點 (.) 按字符串路徑解析。

 

狀態碼

如前面所說,默認情況下,響應的狀態碼總是200,除了 POST 請求外,此時它是201,我們可以通過在處理程序層添加@HttpCode(...) 裝飾器來輕鬆更改此行爲。

@Post()
@HttpCode(204)
create() {
  return 'This action adds a new cat';
}Copy to clipboardErrorCopied

HttpCode 需要從 @nestjs/common 包導入。

通常,狀態碼不是固定的,而是取決於各種因素。在這種情況下,您可以使用類庫特有的的響應(通過@Res()注入 )對象(或者,在出現錯誤時,拋出異常)。

 

Headers

要指定自定義響應頭,可以使用 @header() 修飾器或類庫特有的響應對象,(使用 並 res.header()直接調用)。

@Post()
@Header('Cache-Control', 'none')
create() {
  return 'This action adds a new cat';
}Copy to clipboardErrorCopied

Header 需要從 @nestjs/common 包導入。

 

重定向

要將響應重定向到特定的 URL,可以使用 @Redirect()裝飾器或特定於庫的響應對象(並直接調用 res.redirect())。

@Redirect() 帶有必需的 url參數和可選的 statusCode參數。 如果省略,則 statusCode 默認爲 302

@Get()
@Redirect('https://nestjs.com', 301)Copy to clipboardErrorCopied

有時您可能想動態確定HTTP狀態代碼或重定向URL。通過從路由處理程序方法返回一個形狀爲以下形式的對象:

{
  "url": string,
  "statusCode": number
}Copy to clipboardErrorCopied

返回的值將覆蓋傳遞給 @Redirect()裝飾器的所有參數。 例如:

@Get('docs')
@Redirect('https://docs.nestjs.com', 302)
getDocs(@Query('version') version) {
  if (version && version === '5') {
    return { url: 'https://docs.nestjs.com/v5/' };
  }
}Copy to clipboardErrorCopied

 

路由參數

當您需要接受動態數據作爲請求的一部分時(例如,使用GET /cats/1來獲取 id爲 1的 cat),帶有靜態路徑的路由將無法工作。爲了定義帶參數的路由,我們可以在路由中添加路由參數標記,以捕獲請求 URL 中該位置的動態值。@Get() 下面的裝飾器示例中的路由參數標記演示了此用法。可以使用 @Param() 裝飾器訪問以這種方式聲明的路由參數,該裝飾器應添加到函數簽名中。

@Get(':id')
findOne(@Param() params): string {
  console.log(params.id);
  return `This action returns a #${params.id} cat`;
}Copy to clipboardErrorCopied

@Param()用於修飾方法參數(上面示例中的參數),並使路由參數可用作該修飾的方法參數在方法體內的屬性。 如上面的代碼所示,我們可以通過引用 params.id來訪問 id參數。 您還可以將特定的參數標記傳遞給裝飾器,然後在方法主體中按名稱直接引用路由參數。

Param 需要從 @nestjs/common 包導入。

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

範圍

對於來自不同編程語言背景的人來說,瞭解在 Nest 中幾乎所有內容都可以在傳入的請求之間共享,這讓人意外。比如我們有一個數據庫連接池,具有全局狀態的單例服務等。請記住,Node.js 不遵循請求/響應多線程無狀態模型,每個請求都由主線程處理。因此,使用單例實例對我們的應用程序來說是完全安全的。

但是,存在基於請求的控制器生命週期可能是期望行爲的邊緣情況,例如 GraphQL 應用程序中的請求緩存,比如請求跟蹤或多租戶。在這裏學習如何控制範圍。

 

Async / await

我們喜歡現代 JavaScript,而且我們知道數據讀取大多是異步的。 這就是爲什麼 Nest 支持 async 並且與他們一起工作得非常好。

瞭解更多關於 Async / await 請點擊這裏

每個異步函數都必須返回 Promise。這意味着您可以返回延遲值, 而 Nest 將自行解析它。讓我們看看下面的例子:

cats.controller.ts

@Get()
async findAll(): Promise<any[]> {
  return [];
}Copy to clipboardErrorCopied

這是完全有效的。此外,通過返回 RxJS observable 流。 Nest 路由處理程序更強大。Nest 將自動訂閱下面的源並獲取最後發出的值(在流完成後)。

cats.controller.ts

@Get()
findAll(): Observable<any[]> {
  return of([]);
}Copy to clipboardErrorCopied

上面的方法都可以, 你可以選擇你喜歡的方式。

 

請求負載

之前的 POST 路由處理程序不接受任何客戶端參數。我們在這裏添加 @Body() 參數來解決這個問題。

首先(如果您使用 TypeScript),我們需要確定 DTO(數據傳輸對象)模式。DTO是一個對象,它定義瞭如何通過網絡發送數據。我們可以通過使用 TypeScript接口或簡單的類來完成。令人驚訝的是,我們在這裏推薦使用類。爲什麼?類是JavaScript ES6標準的一部分,因此它們在編譯後的 JavaScript中保留爲實際實體。另一方面,由於 TypeScript接口在轉換過程中被刪除,所以 Nest不能在運行時引用它們。這一點很重要,因爲諸如管道之類的特性在運行時能夠訪問變量的元類型時提供更多的可能性。

我們來創建 CreateCatDto 類:

create-cat.dto.ts

export class CreateCatDto {
  readonly name: string;
  readonly age: number;
  readonly breed: string;
}Copy to clipboardErrorCopied

它只有三個基本屬性。 之後,我們可以在 CatsController中使用新創建的DTO

cats.controller.ts

@Post()
async create(@Body() createCatDto: CreateCatDto) {
  return 'This action adds a new cat';
}Copy to clipboardErrorCopied

 

處理錯誤

這裏有一章關於處理錯誤(即處理異常)的單獨章節。

 

完整示例

下面是一個示例,該示例利用幾個可用的裝飾器來創建基本控制器。 該控制器公開了幾種訪問和操作內部數據的方法。

cats.controller.ts

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`;
  }
}Copy to clipboardErrorCopied

 

最後一步

控制器已經準備就緒,可以使用,但是 Nest 不知道 CatsController 是否存在,所以它不會創建這個類的一個實例。

控制器總是屬於模塊,這就是爲什麼我們將 controllers 數組保存在 @module() 裝飾器中。 由於除了根 ApplicationModule,我們沒有其他模塊,所以我們將使用它來介紹 CatsController

app.module.ts

import { Module } from '@nestjs/common';
import { CatsController } from './cats/cats.controller';

@Module({
  controllers: [CatsController],
})
export class AppModule {}Copy to clipboardErrorCopied

我們使用 @Module()裝飾器將元數據附加到模塊類,Nest 現在可以輕鬆反映必須安裝的控制器。

 

類庫特有方式

到目前爲止,我們已經討論了 Nest 操作響應的標準方式。操作響應的第二種方法是使用類庫特有的響應對象(Response)。爲了注入特定的響應對象,我們需要使用 @Res() 裝飾器。爲了對比差異,我們重寫 CatsController :

import { Controller, Get, Post, Res, HttpStatus } from '@nestjs/common';
import { Response } from 'express';

@Controller('cats')
export class CatsController {
  @Post()
  create(@Res() res: Response) {
    res.status(HttpStatus.CREATED).send();
  }

  @Get()
  findAll(@Res() res: Response) {
     res.status(HttpStatus.OK).json([]);
  }
}Copy to clipboardErrorCopied

雖然這種方法有效,並且事實上通過提供響應對象的完全控制(標準操作,庫特定的功能等)在某些方面允許更多的靈活性,但應謹慎使用。這種方式非常不清晰,並且有一些缺點。 主要是失去了與依賴於 Nest 標準響應處理的 Nest 功能的兼容性,例如攔截器和 @HttpCode() 裝飾器。此外,您的代碼可能變得依賴於平臺(因爲底層庫可能在響應對象上有不同的 API),並且更難測試(您必須模擬響應對象等)。

因此,在可能的情況下,應始終首選 Nest 標準方法。

 

譯者署名

用戶名 頭像 職能 簽名
@zuohuadong 翻譯 專注於 caddy 和 nest,@zuohuadong at Github
@Drixn 翻譯 專注於 nginx 和 C++,@Drixn
@Armor   翻譯 專注於 Java 和 Nest,@Armor
@tangkai 翻譯 專注於 React,@tangkai
@havef 校正 數據分析、機器學習、TS/JS技術棧 @havef

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章