控制器負(fù)責(zé)處理傳入請(qǐng)求并將響應(yīng)返回給客戶端。
控制器的目的是接收應(yīng)用的特定請(qǐng)求。路由機(jī)制控制哪個(gè)控制器接收哪些請(qǐng)求。通常,每個(gè)控制器有多個(gè)路由,不同的路由可以執(zhí)行不同的操作。
為了創(chuàng)建一個(gè)基本的控制器,我們使用類和裝飾器。裝飾器將類與所需的元數(shù)據(jù)相關(guān)聯(lián),并使 Nest 能夠創(chuàng)建路由映射(將請(qǐng)求綁定到相應(yīng)的控制器)。
在下面的例子中,我們使用 @Controller() 裝飾器定義一個(gè)基本的控制器??蛇x 路由路徑前綴設(shè)置為 cats。在 @Controller() 裝飾器中使用路徑前綴可以使我們輕松地對(duì)一組相關(guān)的路由進(jìn)行分組,并最大程度地減少重復(fù)代碼。例如,我們可以選擇將一組用于管理與 /customers 下的客戶實(shí)體進(jìn)行互動(dòng)的路由進(jìn)行分組。這樣,我們可以在 @Controller() 裝飾器中指定路徑前綴 customers,這樣就不必為文件中的每個(gè)路由重復(fù)路徑的那部分。
cats.controller.ts
import { Controller, Get } from '@nestjs/common';
@Controller('cats')
export class CatsController {
@Get()
findAll(): string {
return 'This action returns all cats';
}
}
要使用 CLI 創(chuàng)建控制器,只需執(zhí)行 $ nest g controller cats 命令。
findAll() 方法之前的 @Get() HTTP 請(qǐng)求方法裝飾器告訴 Nest 為 HTTP 請(qǐng)求的特定端點(diǎn)創(chuàng)建處理程序。端點(diǎn)對(duì)應(yīng)于 HTTP 請(qǐng)求方法(在本例中為 GET )和路由路徑(如 GET /customer )。什么是路由路徑 ? 一個(gè)處理程序的路由路徑是通過(guò)連接為控制器 (Controller) 聲明的(可選)前綴和請(qǐng)求裝飾器中指定的任何路徑來(lái)確定的。由于我們已經(jīng)為每個(gè) route(cats) 聲明了一個(gè)前綴,并且沒(méi)有在裝飾器中添加任何路由信息,因此 Nest 會(huì)將 GET /cats 請(qǐng)求映射到此處理程序。如上所述,該路徑包括可選的控制由路徑前綴和請(qǐng)求方法裝飾器中聲明的任何路徑字符串。例如,路徑前綴 customers 與裝飾器 @Get('profile') 組合會(huì)為 GET /customers/profile 請(qǐng)求生成路由映射。
在上面的示例中,當(dāng)對(duì)此端點(diǎn)發(fā)出 GET 請(qǐng)求時(shí), Nest 會(huì)將請(qǐng)求路由到我們的自定義的 findAll() 方法。請(qǐng)注意,我們?cè)诖颂庍x擇的函數(shù)名稱完全是任意的。顯然,我們必須聲明一個(gè)綁定到路由的函數(shù),但 Nest 不會(huì)對(duì)所選的函數(shù)名稱附加任何意義。(譯者注:即路由與處理函數(shù)命名無(wú)關(guān))
此函數(shù)將返回 200 狀態(tài)代碼和相關(guān)的響應(yīng),在本例中只返回了一個(gè)字符串。為什么會(huì)這樣? 為了解釋原因,首先我們將介紹 Nest 使用兩種不同的操作響應(yīng)選項(xiàng)的概念:
標(biāo)準(zhǔn)(推薦) | 使用這個(gè)內(nèi)置方法,當(dāng)請(qǐng)求處理程序返回一個(gè) JavaScript 對(duì)象或數(shù)組時(shí),它將自動(dòng)序列化為 JSON 。但是,當(dāng)它返回一個(gè) JavaScript 基本類型(例如string、number、boolean )時(shí), Nest 將只發(fā)送值,而不嘗試序列化它。這使響應(yīng)處理變得簡(jiǎn)單:只需要返回值,其余的由 Nest 負(fù)責(zé)。 |
類庫(kù)特有的 | 我們可以在函數(shù)簽名處通過(guò) @Res() 注入類庫(kù)特定的響應(yīng)對(duì)象(例如, Express )。使用此方法,你就能使用由該響應(yīng)對(duì)象暴露的原生響應(yīng)處理函數(shù)。例如,使用 Express ,您可以使用 response.status(200).send() 構(gòu)建響應(yīng) |
注意!Nest 檢測(cè)處理程序何時(shí)使用 @Res() 或 @Next(),表明你選擇了特定于庫(kù)的選項(xiàng)。如果在一個(gè)處理函數(shù)上同時(shí)使用了這兩個(gè)方法,那么此處的標(biāo)準(zhǔn)方式就是自動(dòng)禁用此路由, 你將不會(huì)得到你想要的結(jié)果。如果需要在某個(gè)處理函數(shù)上同時(shí)使用這兩種方法(例如,通過(guò)注入響應(yīng)對(duì)象,單獨(dú)設(shè)置 cookie / header,但把其余部分留給框架),你必須在裝飾器 @Res({ passthrough: true }) 中將 passthrough 選項(xiàng)設(shè)為 true
處理程序有時(shí)需要訪問(wèn)客戶端的請(qǐng)求細(xì)節(jié)。Nest 提供了對(duì)底層平臺(tái)(默認(rèn)為 Express)的請(qǐng)求對(duì)象(request)的訪問(wèn)方式。我們可以在處理函數(shù)的簽名中使用 @Req() 裝飾器,指示 Nest 將請(qǐng)求對(duì)象注入處理程序。
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';
}
}
為了在 express 中使用 Typescript (如 request: Request 上面的參數(shù)示例所示),請(qǐng)安裝 @types/express 。
Request 對(duì)象代表 HTTP 請(qǐng)求,并具有查詢字符串,請(qǐng)求參數(shù)參數(shù),HTTP 標(biāo)頭(HTTP header) 和 正文(HTTP body)的屬性(在這里閱讀更多)。在多數(shù)情況下,不必手動(dòng)獲取它們。 我們可以使用專用的裝飾器,比如開(kāi)箱即用的 @Body() 或 @Query() 。 下面是 Nest 提供的裝飾器及其代表的底層平臺(tái)特定對(duì)象的對(duì)照列表。
@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 平臺(tái)(例如,Express 和 Fastify)之間的類型兼容, Nest 提供了 @Res()和 @Response() 裝飾器。@Res() 只是 @Response() 的別名。兩者都直接暴露了底層平臺(tái)的 response 對(duì)象接口。在使用它們時(shí),您還應(yīng)該導(dǎo)入底層庫(kù)的類型聲明(如:@types/express)以充分利用它們。需要注意的是,在請(qǐng)求處理函數(shù)中注入 @Res()或 @Response() 時(shí),會(huì)將 Nest 置于該處理函數(shù)的特定于庫(kù)(Library-specific mode)的模式下,并負(fù)責(zé)管理響應(yīng)。這樣做時(shí),必須通過(guò)調(diào)用 response 對(duì)象(例如,res.json(…) 或 res.send(…))發(fā)出某種響應(yīng),否則 HTTP 服務(wù)器將掛起。
我們已經(jīng)創(chuàng)建了一個(gè)端點(diǎn)來(lái)獲取 cats 的數(shù)據(jù)(GET 路由)。我們通常還希望提供一個(gè)創(chuàng)建新記錄的端點(diǎn)。為此,讓我們創(chuàng)建 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';
}
}
就這么簡(jiǎn)單。 Nest 為所有標(biāo)準(zhǔn)的 HTTP 方法提供了相應(yīng)的裝飾器:@Put()、@Delete()、@Patch()、@Options()、以及 @Head()。此外,@All() 則用于定義一個(gè)用于處理所有 HTTP 請(qǐng)求方法的處理程序。
路由同樣支持模式匹配。例如,星號(hào)被用作通配符,將匹配任何字符組合。
@Get('ab*cd')
findAll() {
return 'This route uses a wildcard';
}
路由路徑 'ab*cd' 將匹配 abcd 、ab_cd 、abecd 等。字符 ? 、+ 、 * 以及 () 是它們的正則表達(dá)式對(duì)應(yīng)項(xiàng)的子集。連字符(-) 和點(diǎn)(.)按字符串路徑逐字解析。
如上所述,默認(rèn)情況下,響應(yīng)的狀態(tài)碼總是默認(rèn)為 200,除了 POST 請(qǐng)求(默認(rèn)響應(yīng)狀態(tài)碼為 201),我們可以通過(guò)在處理函數(shù)外添加 @HttpCode(...) 裝飾器來(lái)輕松更改此行為。
@Post()
@HttpCode(204)
create() {
return 'This action adds a new cat';
}
HttpCode 需要從 @nestjs/common 包導(dǎo)入。
通常,狀態(tài)碼不是固定的,而是取決于各種因素。在這種情況下,您可以使用類庫(kù)特有(library-specific)的 response (通過(guò) @Res()注入 )對(duì)象(或者在出現(xiàn)錯(cuò)誤時(shí),拋出異常)。
要指定自定義響應(yīng)頭,可以使用 @header() 裝飾器或類庫(kù)特有的響應(yīng)對(duì)象,(并直接調(diào)用 res.header())。
@Post()
@Header('Cache-Control', 'none')
create() {
return 'This action adds a new cat';
}
Header 需要從 @nestjs/common 包導(dǎo)入。
要將響應(yīng)重定向到特定的 URL,可以使用 @Redirect() 裝飾器或特定于庫(kù)的響應(yīng)對(duì)象(并直接調(diào)用 res.redirect())。
@Redirect() 裝飾器有兩個(gè)可選參數(shù),url 和 statusCode。 如果省略,則 statusCode 默認(rèn)為 302。
@Get()
@Redirect('https://nestjs.com', 301)
有時(shí)您可能想動(dòng)態(tài)地決定 HTTP 狀態(tài)代碼或重定向 URL。通過(guò)從路由處理方法返回一個(gè)如下格式的對(duì)象:
{
"url": string,
"statusCode": number
}
返回的值將覆蓋傳遞給 @Redirect()裝飾器的所有參數(shù)。 例如:
@Get('docs')
@Redirect('https://docs.nestjs.com', 302)
getDocs(@Query('version') version) {
if (version && version === '5') {
return { url: 'https://docs.nestjs.com/v5/' };
}
}
當(dāng)您需要接受動(dòng)態(tài)數(shù)據(jù)(dynamic data)作為請(qǐng)求的一部分時(shí)(例如,使用GET /cats/1 來(lái)獲取 id 為 1 的 cat),帶有靜態(tài)路徑的路由將無(wú)法工作。為了定義帶參數(shù)的路由,我們可以在路由路徑中添加路由參數(shù)標(biāo)記(token)以捕獲請(qǐng)求 URL 中該位置的動(dòng)態(tài)值。下面的 @Get() 裝飾器示例中的路由參數(shù)標(biāo)記(route parameter token)演示了此用法。以這種方式聲明的路由參數(shù)可以使用 @Param() 裝飾器訪問(wèn),該裝飾器應(yīng)添加到函數(shù)簽名中。
@Get(':id')
findOne(@Param() params): string {
console.log(params.id);
return `This action returns a #${params.id} cat`;
}
@Param() 用于修飾一個(gè)方法的參數(shù)(上面示例中的 params),并在該方法內(nèi)將路由參數(shù)作為被修飾的方法參數(shù)的屬性。如上面的代碼所示,我們可以通過(guò)引用 params.id來(lái)訪問(wèn)(路由路徑中的) id 參數(shù)。 您還可以將特定的參數(shù)標(biāo)記傳遞給裝飾器,然后在方法主體中按參數(shù)名稱直接引用路由參數(shù)。
Param 需要從 @nestjs/common 包導(dǎo)入。
@Get(':id')
findOne(@Param('id') id): string {
return `This action returns a #${id} cat`;
}
@Controller 裝飾器可以接受一個(gè) host 選項(xiàng),以要求傳入請(qǐng)求的 HTTP 主機(jī)匹配某個(gè)特定值。
@Controller({ host: 'admin.example.com' })
export class AdminController {
@Get()
index(): string {
return 'Admin page';
}
}
由于 Fastify 缺乏對(duì)嵌套路由器的支持,因此當(dāng)使用子域路由時(shí),應(yīng)該改用(默認(rèn)) Express 適配器(Express adapter)。
與一個(gè)路由路徑 path 類似,該 hosts 選項(xiàng)可以使用參數(shù)標(biāo)識(shí)(token)來(lái)捕獲主機(jī)名中該位置的動(dòng)態(tài)值。下面的 @Controller() 裝飾器示例中的主機(jī)參數(shù)標(biāo)識(shí)(host parameter token)演示了此用法??梢允褂?nbsp;@HostParam() 裝飾器訪問(wèn)以這種方式聲明的主機(jī)參數(shù),該裝飾器應(yīng)添加到方法簽名中。
@Controller({ host: ':account.example.com' })
export class AccountController {
@Get()
getInfo(@HostParam('account') account: string) {
return account;
}
對(duì)于來(lái)自不同編程語(yǔ)言背景的人來(lái)說(shuō),可能對(duì) Nest 中幾乎所有內(nèi)容都可以在傳入的請(qǐng)求之間共享感到非常意外。例如,我們有一個(gè)數(shù)據(jù)庫(kù)連接池,具有全局狀態(tài)的單例服務(wù)等。請(qǐng)記住,Node.js 并不遵循請(qǐng)求/響應(yīng)多線程無(wú)狀態(tài)模型(在該模型中,每個(gè)請(qǐng)求都由單獨(dú)的線程處理),在 Nest 中,每個(gè)請(qǐng)求都由主線程處理。因此,使用單例實(shí)例對(duì)我們的應(yīng)用程序來(lái)說(shuō)是完全安全的。
但是,存在基于請(qǐng)求的控制器生命周期可能是期望行為的邊緣情況,例如 GraphQL 應(yīng)用程序中的請(qǐng)求緩存,請(qǐng)求跟蹤或多租戶。在這里學(xué)習(xí)如何控制作用域。
我們酷愛(ài)現(xiàn)代 Javascript,并且我們知道數(shù)據(jù)讀取(data extraction)大多是異步的.這就是為什么 Nest 完美支持異步函數(shù)(Async Function)特性的原因。
了解更多關(guān)于 Async / await 請(qǐng)點(diǎn)擊這里
每個(gè)異步函數(shù)都必須返回一個(gè) Promise。這意味著您可以返回延遲值,而 Nest 將自行解析它。讓我們看看下面這個(gè)例子:
cats.controller.ts
@Get()
async findAll(): Promise<any[]> {
return [];
}
這是完全有效的。此外,通過(guò)返回 RxJS observable 流,Nest 路由處理程序?qū)⒏訌?qiáng)大。 Nest 將自動(dòng)訂閱下面的源并獲取最后發(fā)出的值(在流完成后)。
cats.controller.ts
@Get()
findAll(): Observable<any[]> {
return of([]);
}
上述的兩種方法都是可行的,你可以選擇你喜歡的方式。
此前我們列舉的的 POST 路由處理程序樣例中,處理程序沒(méi)有接受任何客戶端參數(shù)。我們?cè)谶@里通過(guò)添加 @Body() 參數(shù)來(lái)解決這個(gè)問(wèn)題。
首先(如果您使用 TypeScript),我們需要確定 DTO(數(shù)據(jù)傳輸對(duì)象)模式。DTO是一個(gè)對(duì)象,它定義了如何通過(guò)網(wǎng)絡(luò)發(fā)送數(shù)據(jù)。我們可以通過(guò)使用 TypeScript 接口(Interface)或簡(jiǎn)單的類(Class)來(lái)定義 DTO 模式。有趣的是,我們?cè)谶@里推薦使用類。為什么?類是 JavaScript ES6 標(biāo)準(zhǔn)的一部分,因此它們?cè)诰幾g后的 JavaScript 中被保留為實(shí)際實(shí)體。另一方面,由于 TypeScript 接口在轉(zhuǎn)換過(guò)程中被刪除,所以 Nest 不能在運(yùn)行時(shí)引用它們。這一點(diǎn)很重要,因?yàn)橹T如管道(Pipe)之類的特性為在運(yùn)行時(shí)訪問(wèn)變量的元類型提供更多的可能性。
現(xiàn)在,我們來(lái)創(chuàng)建 CreateCatDto 類:
/*
create-cat.dto.ts
*/
export class CreateCatDto {
readonly name: string;
readonly age: number;
readonly breed: string;
}
它只有三個(gè)基本屬性。 之后,我們可以在 CatsController 中使用新創(chuàng)建的DTO:
cats.controller.ts
@Post()
async create(@Body() createCatDto: CreateCatDto) {
return 'This action adds a new cat';
}
這里有一個(gè)關(guān)于處理錯(cuò)誤(即處理異常)的單獨(dú)章節(jié)。
下面是一個(gè)示例,該示例利用幾個(gè)可用的裝飾器來(lái)創(chuàng)建基本控制器。 該控制器暴露了幾個(gè)訪問(wèn)和操作內(nèi)部數(shù)據(jù)的方法。
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 提供了一個(gè)能夠自動(dòng)生成所有這些模板代碼的生成器,它幫助我們規(guī)避手動(dòng)建立這些文件,并使開(kāi)發(fā)體驗(yàn)變得更加簡(jiǎn)單。在這里閱讀關(guān)于該功能的更多信息。
控制器已經(jīng)準(zhǔn)備就緒,可以使用,但是 Nest 依然不知道 CatsController 是否存在,所以它不會(huì)創(chuàng)建這個(gè)類的一個(gè)實(shí)例。
控制器總是屬于模塊,這就是為什么我們?cè)?nbsp;@Module() 裝飾器中包含 controllers 數(shù)組的原因。 由于除了根模塊 AppModule之外,我們還沒(méi)有定義其他模塊,所以我們將使用它來(lái)介紹 CatsController:
app.module.ts
import { Module } from '@nestjs/common';
import { CatsController } from './cats/cats.controller';
@Module({
controllers: [CatsController],
})
export class AppModule {}
我們使用 @Module() 裝飾器將元數(shù)據(jù)附加到模塊類中,現(xiàn)在,Nest 可以輕松反射(reflect)出哪些控制器(controller)必須被安裝。
到目前為止,我們已經(jīng)討論了 Nest 操作響應(yīng)的標(biāo)準(zhǔn)方式。操作響應(yīng)的第二種方法是使用類庫(kù)特有的響應(yīng)對(duì)象(Response)。為了注入特定的響應(yīng)對(duì)象,我們需要使用 @Res() 裝飾器。為了對(duì)比差異,讓我們來(lái)重寫(xiě) CatsController:
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([]);
}
}
盡管此方法有效,并且實(shí)際上通過(guò)提供對(duì)響應(yīng)對(duì)象的完全控制(標(biāo)頭操作,特定于庫(kù)的功能等)在某些方面提供了更大的靈活性,但應(yīng)謹(jǐn)慎使用此種方法。通常來(lái)說(shuō),這種方式非常不清晰,并且有一些缺點(diǎn)。 主要的缺點(diǎn)是你的代碼變得依賴于平臺(tái)(因?yàn)椴煌牡讓訋?kù)在響應(yīng)對(duì)象(Response)上可能具有不同的 API),并且更加難以測(cè)試(您必須模擬響應(yīng)對(duì)象等)。
而且,在上面的示例中,你失去與依賴于 Nest 標(biāo)準(zhǔn)響應(yīng)處理的 Nest 功能(例如,攔截器(Interceptors) 和 @HttpCode()/@Header() 裝飾器)的兼容性。要解決此問(wèn)題,可以將 passthrough 選項(xiàng)設(shè)置為 true,如下所示:
@Get()
findAll(@Res({ passthrough: true }) res: Response) {
res.status(HttpStatus.OK);
return [];
}
現(xiàn)在,你就能與底層框架原生的響應(yīng)對(duì)象(Response)進(jìn)行交互(例如,根據(jù)特定條件設(shè)置 Cookie 或 HTTP 頭),并將剩余的部分留給 Nest 處理。
更多建議: