### 控制器
控制器負責處理傳入的**請求**并將**響應**返回給客戶端。

控制器的目的是接收應用程序的特定請求。**路由**機制控制哪個控制器接收哪個請求。很多時候,每個控制器都有多個路由,不同的路由可以執行不同的動作。
為了創建一個基本的控制器,我們使用類和**裝飾器**。裝飾器將類與所需的元數據相關聯,并使 Nest 能夠創建路由映射(將請求綁定到相應的控制器)。
> **提示:** 為了快速創建內置[驗證的 CRUD 控制器,您可以使用 CLI 的](https://docs.nestjs.com/techniques/validation)[CRUD 生成器](https://docs.nestjs.com/recipes/crud-generator#crud-generator):`nest g resource [name]`。
#### 路由[#](#routing)
在下面的示例中,我們將使用`@Controller()`裝飾器,它是定義基本控制器所**必需的。**我們將指定一個可選的路由路徑前綴`cats`。在裝飾器中使用路徑前綴`@Controller()`可以讓我們輕松地對一組相關路由進行分組,并最大限度地減少重復代碼。例如,我們可以選擇將一組用于管理與`/customers`下的客戶實體進行互動的路由進行分組。這樣,我們可以在`@Controller()`裝飾器中指定路徑前綴`customers`,這樣我們就不必為文件中的每個路由重復該部分路徑。
/\* cats.controller.ts \*/
~~~typescript
import { Controller, Get } from '@nestjs/common';
@Controller('cats')
export class CatsController {
@Get()
findAll(): string {
return 'This action returns all cats';
}
}
~~~
> **提示**:要使用 CLI 創建控制器,只需執行`$ nest g controller cats`命令。
`findAll()` 方法之前的`@Get()`HTTP 請求方法裝飾器告訴 Nest 為 HTTP 請求的特定端點創建處理程序。端點對應于 HTTP 請求方法(本例中為 GET)和路由路徑。什么是路由路徑?處理程序的路由路徑是通過連接為控制器聲明的(可選)前綴和方法的裝飾器中指定的任何路徑來確定的。由于我們已經為每個路由 (`cats`) 聲明了一個前綴,并且沒有在裝飾器中添加任何路徑信息,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 請求。我們可以通過在處理程序級別添加裝飾器來輕松更改此行為(請參閱狀態碼)。 |
| 類庫特有的 | 我們可以在函數簽名處通過 `@Res()` 注入類庫特定的響應對象(例如, `Express`)。使用此方法,你就能使用由該響應對象暴露的原生響應處理函數。例如,使用 `Express`,您可以使用 `response.status(200).send()` 構建響應 |
> **警告**:Nest 檢測處理程序何時使用`@Res()`or`@Next()`,表明您選擇了特定于庫的選項。如果同時使用這兩種方法,則該單一路線的標準方法會**自動禁用**,并且將不再按預期工作。要同時使用這兩種方法(例如,通過注入響應對象以僅設置 cookie/標頭但仍將其余部分留給框架),你必須在裝飾器`@Res({ passthrough: true })`中將`passthrough`選項設為`true`
#### 請求對象[#](#request-object)
處理程序有時需要訪問客戶端的**請求**細節。Nest 提供了對底層平臺(默認為`Express`)的[**請求對象**](http://expressjs.com/en/api.html#req)(`request`)的訪問方式。我們可以在處理函數的簽名中使用`@Req()`裝飾器,指示 Nest 將請求對象注入處理程序。
/\* cats.controller.ts \*/
~~~typescript
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';
}
}
~~~
> **提示**:為了利用`express`類型(如`request: Request`上面的參數示例),安裝`@types/express`包。
`Request`對象代表`HTTP`請求,并具有請求查詢字符串、參數、HTTP 標頭(HTTP header) 和 正文(HTTP body)的屬性([在此處](https://expressjs.com/en/api.html#req)閱讀更多內容)。在大多數情況下,沒有必要手動獲取這些屬性。我們可以使用開箱即用的專用裝飾器,例如`@Body()`或者`@Query()`。下面是Nest提供的裝飾器列表以及它們所代表的底層平臺特定對象的對照列表。
| | |
| ------------------------- | --------------------------------- |
| `@Request(),@Req()` | `req` |
| `@Response(),@Res()*` | `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]` |
| `@Ip()` | `req.ip` |
| `@HostParam()` | `req.hosts` |
為了與底層 HTTP 平臺(例如 `Express` 和 `Fastify`)的類型兼容,Nest 提供`@Res()`和`@Response()`裝飾器。`@Res()`只是 . 的別名`@Response()`。兩者都直接暴露了底層的原生平臺`response`對象接口。使用它們時,您還應該導入底層庫的類型(例如,`@types/express`)以充分利用它們。請注意,當您注入其中一個`@Res()`或`@Response()`方法處理程序時,您會將 Nest 置于該處理程序**的庫特定模式**,并且您將負責管理響應。`response`這樣做時,您必須通過調用對象(例如,`res.json(...)`或)來發出某種響應`res.send(...)`,否則 HTTP 服務器將掛起。
> **提示**:要了解如何創建自己的自定義裝飾器,請訪問[本章](https://docs.nestjs.com/custom-decorators)。
#### 資源[#](#resources)
之前,我們定義了一個端點來獲取`cats`資源(**GET**路由)。我們通常還希望提供一個創建新記錄的端點。為此,讓我們創建**POST**處理程序:
/\* cats.controller.ts \*/
~~~typescript
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 方法提供裝飾器:`@Get()`、`@Post()`、`@Put()`、`@Delete()`、`@Patch()`、`@Options()`和`@Head()`.此外,`@All()`定義一個用于處理所有 HTTP 請求方法的處理程序。
#### 路由通配符[#](#route-wildcards)
路由也支持基于模式匹配。例如,星號用作通配符,將匹配任何字符組合。
~~~typescript
@Get('ab*cd')
findAll() {
return 'This route uses a wildcard';
}
~~~
`'ab*cd'`路由路徑將匹配、`abcd`、`ab_cd`等`abecd`。字符`?`,`+`,`*`, 和`()`可以在路由路徑中使用,并且是它們的正則表達式對應項的子集。連字符 (`-`) 和點 (`.`) 由基于字符串的路徑逐字解析。
#### 狀態碼[#](#status-code)
如上所述,默認情況下響應**狀態代碼**始終為**200**,但 POST 請求(**默認響應狀態碼為201**)除外。`@HttpCode(...)`我們可以通過在處理程序級別添加裝飾器來輕松更改此行為。
~~~typescript
@Post()
@HttpCode(204)
create() {
return 'This action adds a new cat';
}
~~~
> **提示:**`HttpCode`從`@nestjs/common`包中導入。
通常,您的狀態代碼不是靜態的,而是取決于各種因素。在這種情況下,您可以使用類庫特有(library-specific)的**`response`**(使用 注入`@Res()`)對象(或者如果出現錯誤,則拋出異常)。
#### 標頭[#](#headers)
要指定自定義響應標頭,您可以使用`@Header()`裝飾器或特定于庫的響應對象(并`res.header()`直接調用)。
~~~typescript
@Post()
@Header('Cache-Control', 'none')
create() {
return 'This action adds a new cat';
}
~~~
> **提示**:`Header`從`@nestjs/common`包中導入。
#### 重定向[#](#redirection)
要將響應重定向到特定 URL,您可以使用`@Redirect()`裝飾器或類庫特有的響應對象(并`res.redirect()`直接調用)。
`@Redirect()`接受兩個參數,`url`和`statusCode`,兩者都是可選的。如果省略,`statusCode`則默認值為`302(Found)`。
~~~typescript
@Get()
@Redirect('https://nestjs.com', 301)
~~~
有時您可能希望動態確定 HTTP 狀態代碼或重定向 URL。通過從路由處理方法返回一個如下格式的對象來做到這一點:
~~~json
{
"url": string,
"statusCode": number
}
~~~
返回值將覆蓋傳遞給`@Redirect()`裝飾器的所有參數。例如:
~~~typescript
@Get('docs')
@Redirect('https://docs.nestjs.com', 302)
getDocs(@Query('version') version) {
if (version && version === '5') {
return { url: 'https://docs.nestjs.com/v5/' };
}
}
~~~
#### 路由參數[#](#route-parameters)
當您需要接受**動態數據**(dynamic data)作為請求的一部分時(例如,使用`GET /cats/1`來獲取 id 為`1`的`cat`),具有靜態路徑的路由將不起作用。為了定義帶參數的路由,我們可以在路由的路徑中添加路由參數**標記(token)**,以捕獲請求 URL 中該位置的動態值。下面裝飾器示例中的路由參數標記`@Get()`演示了這種用法。以這種方式聲明的路由參數可以使用`@Param()`裝飾器訪問,該裝飾器應添加到函數簽名中。
~~~typescript
@Get(':id')
findOne(@Param() params): string {
console.log(params.id);
return `This action returns a #${params.id} cat`;
}
~~~
`@Param()`用于裝飾方法參數(`params`在上面的示例中),并使**路由**參數可用作方法主體內該裝飾方法參數的屬性。如上面的代碼所示,我們可以`id`通過引用來訪問參數`params.id`。您也可以將特定的參數標記傳遞給裝飾器,然后在方法體中直接通過名稱引用路由參數。
> **提示:**`Param`從`@nestjs/common`包中導入。
~~~typescript
@Get(':id')
findOne(@Param('id') id: string): string {
return `This action returns a #${id} cat`;
}
~~~
#### 子域路由[#](#sub-domain-routing)
裝飾器`@Controller`可以接受一個`host`選項,以要求傳入請求的 HTTP 主機匹配某個特定值。
~~~typescript
@Controller({ host: 'admin.example.com' })
export class AdminController {
@Get()
index(): string {
return 'Admin page';
}
}
~~~
> **警告:**由于**Fastify**缺乏對嵌套路由器的支持,所以在使用子域路由時,應該使用(默認)`Express` 適配器。
與一個路由路徑`path`類似,該`hosts`選項可以使用標記來捕獲主機名中該位置的動態值。下面裝飾器示例中的主機參數標記`@Controller()`演示了這種用法。以這種方式聲明的主機參數可以使用`@HostParam()`裝飾器訪問,該裝飾器應添加到方法簽名中。
~~~typescript
@Controller({ host: ':account.example.com' })
export class AccountController {
@Get()
getInfo(@HostParam('account') account: string) {
return account;
}
}
~~~
#### 作用域[#](#scopes)
對于來自不同編程語言背景的人來說,在 Nest 中得知幾乎所有內容都是在傳入請求之間共享的,這可能是出乎意料的。我們有一個到數據庫的連接池、具有全局狀態的單例服務等。請記住,Node.js 不遵循請求/響應多線程無狀態模型,其中每個請求都由單獨的線程處理。因此,使用單例實例對我們的應用程序來說是完全**安全**的。
但是,在某些極端情況下,控制器的基于請求的生命周期可能是期望行為的邊緣情況,例如 GraphQL 應用程序中的每個請求緩存、請求跟蹤或多租戶。[在此處](https://docs.nestjs.com/fundamentals/injection-scopes)了解如何控制作用域。
#### 異步[#](#asynchronicity)
我們喜歡現代 JavaScript,而且我們知道數據提取(data extraction)大多數是**異步**的。這就是為什么 Nest 支持并很好地使用`async`函數的原因。
> **提示:**[在此處](https://kamilmysliwiec.com/typescript-2-1-introduction-async-await)了解有關`async / await`功能的?更多信息[](https://kamilmysliwiec.com/typescript-2-1-introduction-async-await)
每個異步函數都必須返回一個`Promise`.這意味著您可以返回 Nest 能夠自行解析的延遲值。讓我們看一個例子:
/* cats.controller.ts */
```typescript
@Get()
async findAll(): Promise<any[]> {
return [];
}
```
上面的代碼是完全有效的。此外,由于能夠返回 RxJS[可觀察流](http://reactivex.io/rxjs/class/es6/Observable.js~Observable.html),Nest 路由處理程序更加強大。Nest 將自動訂閱下面的源并獲取最后一個發出的值(在流完成)。
```typescript
/* cats.controller.ts */
@Get()
findAll(): Observable<any[]> {
return of([]);
}
```
上述的兩種方法都是有效的,你可以選擇你喜歡的方式。
#### 請求負載[#](#request-payloads)
我們之前的 POST 路由處理程序示例沒有接受任何客戶端參數,讓我們通過在此處添加`@Body()`裝飾器來解決此問題。
首先(如果您使用 TypeScript),我們需要確定**DTO**(數據傳輸對象)模式。`DTO` 是一個對象,定義數據如何通過網絡發送的。我們可以使用**TypeScript**接口或簡單的類來確定 DTO 模式。有趣的是,我們建議在這里使用**類**。為什么?類是 JavaScript ES6 標準的一部分,因此它們在編譯后的 JavaScript 中被保留為真實實體。另一方面,由于 TypeScript 接口在轉譯過程中被移除,所以Nest 無法在運行時引用它們。這很重要,因為諸如**管道**(Pipe)之類的特性為在運行時訪問變量的元類型提供更多的可能性。
/* create-cat.dto.ts */
```typescript
export class CreateCatDto {
readonly name: string;
readonly age: number;
readonly breed: string;
}
```
它只有三個基本屬性。此后,我們可以在 `CatsController`中使用新創建的 `DTO`:
/* cats.controller.ts */
```typescript
@Post()
async create(@Body() createCatDto: CreateCatDto) {
return 'This action adds a new cat';
}
```
> **提示**:我們在`ValidationPipe`中可以過濾掉不應被方法處理程序接收的屬性。在這種情況下,我們可以將可接受的屬性列入白名單,任何未包含在白名單中的屬性都會自動從結果對象中剝離。在`CreateCatDto`示例中,我們的白名單是`name`、`age`和`breed`屬性。[在這里](https://docs.nestjs.com/techniques/validation#stripping-properties)了解更多。
#### 處理錯誤[#](#handling-errors)
[這里](https://docs.nestjs.com/exceptionfilters)有一個單獨的章節介紹處理錯誤(即處理異常)。
#### 完整資源樣本[#](https://docs.nestjs.com/controllers#full-resource-sample)
下面是一個使用幾個可用裝飾器來創建基本控制器的示例。這個控制器公開了幾個方法來訪問和操作內部數據。
```typescript
/* 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`;
}
}
```
> **提示**:Nest CLI 提供了一個生成器(原理圖),它會自動生成**所有樣板代碼**,以幫助我們避免所有這些,并使開發人員的體驗更加簡單。[在此處](https://docs.nestjs.com/recipes/crud-generator)閱讀有關此功能的更多信息。
#### 啟動并運行[#](#getting-up-and-running)
完全定義上述控制器后,Nest 仍然不知道它的`CatsController`存在,因此不會創建此類的實例。
控制器總是屬于一個模塊,這就是我們在裝飾器中包含`controllers`數組的原因。`@Module()`由于我們還沒有定義除 root 之外的任何其他模塊`AppModule`,我們將使用它來介紹`CatsController`:
```typescript
/* app.module.ts */
import { Module } from '@nestjs/common';
import { CatsController } from './cats/cats.controller';
@Module({
controllers: [CatsController],
})
export class AppModule {}
```
我們使用裝飾器將元數據附加到模塊類`@Module()`,Nest 現在可以輕松地反映必須安裝哪些控制器。
#### 特定于庫的方法[#](#library-specific-approach)
到目前為止,我們已經討論了 Nest 處理響應的標準方法。操作響應的第二種方法是使用特定于庫的[響應對象](https://expressjs.com/en/api.html#res)。為了注入特定的響應對象,我們需要使用`@Res()`裝飾器。為了顯示差異,讓我們將其重寫`CatsController`為以下內容:
```typescript
/* cats.controller.ts */
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([]);
}
}
```
盡管這種方法有效,并且實際上通過提供對響應對象的完全控制(標頭操作、庫特定功能等)在某些方面提供了更大的靈活性,但應謹慎使用。一般來說,這種方法不太清楚,并且確實有一些缺點。主要缺點是您的代碼變得依賴于平臺(因為底層庫可能在響應對象上有不同的 API),并且更難測試(您必須模擬響應對象等)。
此外,在上面的示例中,您失去了與依賴于 Nest 標準響應處理的 Nest 功能的兼容性,例如攔截器和`@HttpCode()`/`@Header()`裝飾器。要解決此問題,您可以將`passthrough`選項設置為`true`,如下所示:
```typescript
@Get()
findAll(@Res({ passthrough: true }) res: Response) {
res.status(HttpStatus.OK);
return [];
}
```
現在您可以與本機響應對象進行交互(例如,根據特定條件設置 cookie 或標頭),但將其余部分留給框架。
- 介紹
- 概述
- 第一步
- 控制器
- 提供者
- 模塊
- 中間件
- 異常過濾器
- 管道
- 守衛
- 攔截器
- 自定義裝飾器
- 基礎知識
- 自定義提供者
- 異步提供者
- 動態模塊
- 注入作用域
- 循環依賴
- 模塊參考
- 懶加載模塊
- 應用上下文
- 生命周期事件
- 跨平臺
- 測試
- 技術
- 數據庫
- Mongo
- 配置
- 驗證
- 緩存
- 序列化
- 版本控制
- 定時任務
- 隊列
- 日志
- Cookies
- 事件
- 壓縮
- 文件上傳
- 流式處理文件
- HTTP模塊
- Session(會話)
- MVC
- 性能(Fastify)
- 服務器端事件發送
- 安全
- 認證(Authentication)
- 授權(Authorization)
- 加密和散列
- Helmet
- CORS(跨域請求)
- CSRF保護
- 限速
- GraphQL
- 快速開始
- 解析器(resolvers)
- 變更(Mutations)
- 訂閱(Subscriptions)
- 標量(Scalars)
- 指令(directives)
- 接口(Interfaces)
- 聯合類型
- 枚舉(Enums)
- 字段中間件
- 映射類型
- 插件
- 復雜性
- 擴展
- CLI插件
- 生成SDL
- 其他功能
- 聯合服務
- 遷移指南
- Websocket
- 網關
- 異常過濾器
- 管道
- 守衛
- 攔截器
- 適配器
- 微服務
- 概述
- Redis
- MQTT
- NATS
- RabbitMQ
- Kafka
- gRPC
- 自定義傳輸器
- 異常過濾器
- 管道
- 守衛
- 攔截器
- 獨立應用
- Cli
- 概述
- 工作空間
- 庫
- 用法
- 腳本
- Openapi
- 介紹
- 類型和參數
- 操作
- 安全
- 映射類型
- 裝飾器
- CLI插件
- 其他特性
- 遷移指南
- 秘籍
- CRUD 生成器
- 熱重載
- MikroORM
- TypeORM
- Mongoose
- 序列化
- 路由模塊
- Swagger
- 健康檢查
- CQRS
- 文檔
- Prisma
- 靜態服務
- Nest Commander
- 問答
- Serverless
- HTTP 適配器
- 全局路由前綴
- 混合應用
- HTTPS 和多服務器
- 請求生命周期
- 常見錯誤
- 實例
- 遷移指南
- 發現
- 誰在使用Nest?