Nest系列教程之控制器
控制器層負責處理傳入的請求, 並返回對客戶端的響應。
為了建立一個基本的控制器,我們必須將元資料附加到類中。Nest 知道如何對映我們的控制器到相應的路由。
控制器的定義
下面我們來定義一個 UsersController 控制器,如果使用 ofollow,noindex">Nest CLI 的話,可以在命令列執行以下命令:
$ nest generate controller users
該命令執行後,命令列會輸出以下資訊:
CREATE /src/users/users.controller.spec.ts (478 bytes) CREATE /src/users/users.controller.ts (99 bytes) UPDATE /src/app.module.ts (386 bytes)
通過觀察以上輸出資訊,我們知道該命令 “幕後” 主要做了以下兩件事:
- 新增 users.controller.spec.ts 和 users.controller.ts 檔案
- 更新 src/app.module.ts
users.controller.spec.ts 檔案是用於寫測試用例,這裡我們就不分析了。我們來分析一下 users.controller.ts:
import { Controller } from '@nestjs/common'; @Controller('users') export class UsersController {}
在上面的示例中,我們在 UsersController 類上使用了 @Controller('users')
裝飾器。 users
是每個類中註冊每個路由時的可選字首。使用字首的目的是為了避免所有路由共享通用的字首時出現衝突的情況。
接下來我們來繼續分析一下 src/app.module.ts 更新了哪些內容:
更新前:
import { Module } from '@nestjs/common'; import { AppController } from './app.controller'; import { AppService } from './app.service'; @Module({ imports: [], controllers: [AppController], providers: [AppService], }) export class AppModule {}
更新後:
import { Module } from '@nestjs/common'; import { AppController } from './app.controller'; import { AppService } from './app.service'; import { UsersController } from './users/users.controller'; @Module({ imports: [], controllers: [AppController, UsersController], providers: [AppService], }) export class AppModule {}
通過比較更新前和更新後的檔案,我們知道 UPDATE /src/app.module.ts
所執行的操作就是在 AppModule 根模組註冊新建的 UsersController
。現在我們來簡單總結一下,在 Nest.js 中自定義控制器的流程:
- 建立新的控制器類;
- 使用 @Controller 裝飾器裝飾新的類;
- 在相應的模組中註冊新建的控制器。
接下來我們來更新一下 UsersController,新增相關程式碼具體如下:
import { Controller, Get } from '@nestjs/common'; @Controller('users') export class UsersController { @Get() getAllUsers() { return [{ name: 'semlinker', age: 32 }]; } @Get(':id') getUser() {} }
@Get
方法裝飾器,用於告訴 Nest 建立此路由路徑的端點,並將每個的請求對映到相應的處理程式。由於在定義 UsersController 控制器時,我們使用了 users
路由字首,因此當我們發起 /users
請求時,會呼叫 getAllUsers
方法。
更新完 UsersController 類,我們來測試一下,首先重新啟動一下伺服器:
$ npm run start
然後我們在瀏覽器開啟 http://localhost:3000/users
,正常情況下,你將看到以下的輸出資訊:
[{"name":"semlinker","age":"32"}]
內建裝飾器
在某些情況下,我們需要獲取請求物件,這時我們可以利用 Nest 的 @Req
裝飾器,將請求物件注入處理程式。請求物件會包含查詢引數,HTTP 請求頭和請求體等屬性。但在大多數情況下,我們不必手動獲取它們,因為 Nest 已經為我們提供了對應的裝飾器,對應的關係如下:
@Request() | req |
---|---|
@Response() | res |
@Next() | next |
@Session() | req.session |
@Param(param?: string) | req.params / req.params[param] |
@Body(param?: string) | req.body / req.body[param] |
@Query(param?: string) | req.query / req.query[param] |
@Headers(param?: string) | req.headers / req.headers[param] |
下面我們來更新一下 UsersController
:
import { Controller, Get, Req, Res, Param, HttpStatus } from '@nestjs/common'; @Controller('users') export class UsersController { users = [{ id: 1, name: 'semlinker', age: 32 }]; @Get() getAllUsers(@Res() res) { res.status(HttpStatus.OK).json(this.users); } @Get(':id') getUser(@Req() req, @Res() res, @Param('id') id) { console.log(`Request path: ${req.path}`); console.log(`Resource id: ${id}`); res.status(HttpStatus.OK).json(this.users.find(user => user.id == id)); } }
以上示例,我們使用 @Res()
裝飾器來獲取響應物件,然後設定響應狀態碼和響應資料。同時也介紹了使用 @Req()
和 @Param()
裝飾器來分別獲取請求物件和路由引數。
處理 Post 請求
在介紹如何處理 Post 請求獲取請求體前,我們先來介紹一下 DTO(資料傳輸物件)。DTO 是一個定義如何通過網路傳送資料的物件。我們可以使用 TypeScript 介面或簡單的類來定義物件。但是我們建議在這裡使用類。這是為什麼呢?因為類是 JavaScript ES6 標準的一部分,它們只是簡單的函式。然而 TypeScript 定義的介面在編譯過程中會被移除,導致 Nest 不能引用它們。
現在我們來建立 CreateUserDto
:
export class CreateUserDto { readonly name: string; readonly age: number; }
CreateUserDto 類有兩個屬性。 所有這些都被標記為只讀,因為我們應該儘可能使我們的功能儘可能不被汙染。
建立完 CreateUserDto 類之後,我們來更新一下 UsersController 類,為它新增一個方法用於處理新增使用者:
import { Controller, Get, Req, Res, Param, Post, Body, HttpStatus } from '@nestjs/common'; import { CreateUserDto } from './create-user.dto'; @Controller('users') export class UsersController { users = [{ id: 1, name: 'semlinker', age: 32 }]; @Get() getAllUsers(@Res() res) { res.status(HttpStatus.OK).json(this.users); } @Get(':id') getUser(@Req() req, @Res() res, @Param('id') id) { console.log(`Request path: ${req.path}`); console.log(`Resource id: ${id}`); res.status(HttpStatus.OK).json(this.users.find(user => user.id == id)); } @Post() createUser(@Res() res, @Body() createUserDto: CreateUserDto) { console.dir(createUserDto); res.status(HttpStatus.CREATED).send(); } }
在上面程式碼中,我們通過 @Body()
裝飾器獲取 Post 請求體的內容,然後通過 @Res()
獲取響應物件,進而設定響應狀態碼。好的,現在我們來驗證一下,看看是否能正常處理 Post 請求。
首先執行以下命令啟動我們的應用程式:
$ npm run start
因為我使用的是 Visual Studio Code ,所以我將使用 REST Client 這款功能強大的 HTTP Client 外掛,來發送 HTTP 請求。對於其他的小夥伴來說,也可以使用其它的 HTTP Client,如 Postman、Paw 或 Fiddler 等。
接著我們在專案根目錄下建立一個 app.http
檔案並輸入以下內容:
POST http://127.0.0.1:3000/users HTTP/1.1 content-type: application/json { "name": "lolo", "age": 3 }
然後開啟 app.http
檔案,右鍵選擇 Send Request
選單,如果一切正常的話,終端會輸出以下訊息:
{ name: 'lolo', age: 3 }
此外 REST Client 還會為我們自動開啟一個新的視窗,用於顯示 HTTP 響應報文:
HTTP/1.1 201 Created X-Powered-By: Express Date: Mon, 17 Sep 2018 05:59:40 GMT Connection: keep-alive Content-Length: 0
狀態碼和響應頭
前面我們已經介紹了 @Get()
、 @Post()
、 @Req()
、 @Param()
、 @Body()
和 @Res()
裝飾器,下面我們再來介紹 @HttpCode()
和 @Header()
這個兩個裝飾器。顧名思義,它們分別用來設定狀態碼和響應頭,使用示例如下:
@Post() @HttpCode(201) @Header('Cache-Control', 'none') createUser(@Body() createUserDto: CreateUserDto) { console.dir(createUserDto); return { success: true }; }
更新完上述程式碼,我們重啟一下伺服器,然後再使用 REST Client 傳送請求,之後我們再來檢視 HTTP 響應結果:
HTTP/1.1 201 Created X-Powered-By: Express Cache-Control: none Content-Type: application/json; charset=utf-8 Content-Length: 16 ETag: W/"10-oV4hJxRVSENxc/wX8+mA4/Pe4tA" Date: Mon, 17 Sep 2018 08:36:26 GMT Connection: keep-alive { "success": true }
最後我們來介紹 Nest 支援的一個很強大的特性 —— Async / await。
Async / await
Nest 不但支援非同步函式,而且還支援 RxJS Observable 流。這對於喜歡 Angular 或 RxJS 的開發者來說,是一個福音。下面我們來簡單看一下示例:
- 非同步函式
@Get() async findAll(): Promise<any[]> { return []; }
- RxJS Observable
@Get() findAll(): Observable<any[]> { return of([]); }