在线文档教程

控制器

控制器

控制器负责处理传入的请求并将响应返回给客户端。

控制器的目的是接收应用程序的特定请求。路由策略控制着哪些控制器接收哪些请求。通常,每个控制器具有多个路由,并且不同的路由可以执行不同的动作。

为了创建一个基本的控制器,我们使用类和装饰器装饰器将类与所需的元数据相关联,并使Nest能够创建路由映射(将请求绑定到相应的控制器)。

路由

在下面的示例中,我们将使用@Controller()装饰器,它是定义基本控制器所必需的。我们将指定一个可选的路由路径前缀cats。在@Controller()装饰器中使用路径前缀可以使我们轻松地对一组相关的路由进行分组,并最大程度地减少重复代码。例如,我们可以选择对一组路由进行分组,这些路由管理与该路由下的客户实体的交互/customers。在这种情况下,我们可以customers@Controller()装饰器中指定路径前缀,这样就不必为文件中的每个路由重复路径的那部分。

cats.controller.ts

JS

import { Controller, Get } from '@nestjs/common'; @Controller('cats') export class CatsController { @Get() findAll() { return 'This action returns all cats'; } }

提示要使用CLI创建控制器,只需执行$ nest g controller cats命令即可。

findAll()方法前面的@Get()装饰器告诉Nest创建一个特定的路由路径端点并将每个相应的请求映射到该处理程序。由于我们已经为每个route(cats)声明了一个前缀,因此Nest会将每个/catsGET请求映射到此方法。

在上面的示例中,当对此端点发出GET请求时,Nest将请求路由到我们的用户定义的findAll()方法。请注意,我们在此处选择的方法名称完全是任意的。显然,我们必须声明一个将路由绑定到的方法,但是Nest不对选择的方法名称赋予任何意义。

标准(推荐)当我们返回一个JavaScript对象或数组时,它将自动序列化为JSON。但是,当我们返回一个字符串时,Nest将只发送一个字符串而不尝试序列化它。此外,默认情况下,响应的状态代码始终为200,但使用201的 POST请求除外。我们可以通过@HttpCode(...)在处理程序级别添加装饰器来轻松更改此行为。
指定库我们可以使用库特定的响应对象,我们可以使用@Res()函数签名中的装饰器注入(例如findAll(@Res() response))。通过这种方法,您可以使用该对象公开的本机响应处理方法。例如,使用Express,您可以使用代码来构造响应response.status(200).send()。

警告禁止同时使用这两种方法。Nest检测处理程序是否正在使用@Res()@Next()。如果两种方法同时使用 - 标准方法将自动禁用此单一路径,并且将不再按预期工作。

请求对象

处理程序通常需要访问客户端请求详细信息。实际上,Nest使用特定于库(默认情况下为express)的请求对象。因此,我们可以强制Nest使用@Req()装饰器将请求对象注入到处理程序中,从而访问该请求对象。

cats.controller.ts

JS

import { Controller, Get, Req } from '@nestjs/common'; @Controller('cats') export class CatsController { @Get() findAll(@Req() request) { return 'This action returns all cats'; } }

request对象表示HTTP请求,并具有请求查询字符串,参数,HTTP标头和正文的属性。在大多数情况下,没有必要手动获取这些属性。我们可以使用专用的装饰器,例如@Body()or @Query(),它们是开箱即用的。下面是提供的装饰器和它们代表的普通快速对象的比较。

@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]

提示要了解如何创建自己的自定义装饰,参观这个章节。

资源

我们定义了一个端点来获取cats资源(GET路由)。提供创建新记录的方法也很棒。为此,让我们创建POST处理程序:

cats.controller.ts

JS

import { Controller, Get, Post } from '@nestjs/common'; @Controller('cats') export class CatsController { @Post() create() { return 'This action adds a new cat'; } @Get() findAll() { return 'This action returns all cats'; } }

就这么简单。Nest 提供装饰器用于所有的标准HTTP方法:@Get()@Post()@Put()@Delete()@Patch()@Options()@Head()@All()。它们都代表各自的HTTP请求方法。

路由通配符

也支持基于模式的路由。例如,星号用作通配符,并且将匹配任何字符组合。

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

上述路线路径匹配abcdab_cdabecd,等等。字符?+*,和()是他们的正则表达式的对应的子集。连字符(-)和点(.)按字面顺序由基于字符串的路径解释。

状态代码

如上所述,默认情况下,响应状态代码始终为200,但POST请求为201。我们可以通过@HttpCode(...)在处理程序级别添加装饰器来轻松更改此行为。

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

通常,您的状态代码不是静态的,而是取决于各种因素。在这种情况下,您可以使用特定于库的响应(注入使用@Res())对象(或者,如果出现错误,则抛出异常)。

响应头

要指定自定义响应标头,可以使用@Header()装饰器或特定于库的响应对象。

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

路由参数

当您需要接受动态数据作为URL的一部分时,具有静态路径的路由无法提供帮助。为了定义带参数的路径,我们可以直接在路由路径中特定路由参数。

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

要获取特定参数,只需在括号中传递其名称即可。

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

异步/ AWAIT

我们喜欢现代JavaScript,我们知道数据提取大多是异步的。这就是为什么Nest支持async功能并与功能很好地配合的原因。

提示:在此处了解有关async / await功能的更多信息

每个异步函数都必须返回一个Promise。这意味着您可以返回Nest能够自行解决的延迟值。我们来看下面的一个例子:

cats.controller.ts

JS

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

以上代码完全有效。此外,通过能够返回RxJS 可观察流,Nest路由处理程序更加强大。Nest将自动订阅下面的源并获取最后发出的值

cats.controller.ts

JS

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

上述任何一种方法都可以使用,您可以使用符合您要求的任何方法。

请求有效负载

我们之前的POST路由处理程序示例不接受任何客户端参数。让我们通过在@Body()这里添加参数来解决这个问题。

但首先(如果您使用TypeScript),我们需要确定DTO(数据传输对象)架构。DTO是一个定义数据如何通过网络发送的对象。我们可以使用TypeScript接口或简单来确定DTO模式。令人惊讶的是,我们建议在这里使用。为什么?是JavaScript ES6标准的一部分,因此它们代表普通函数。另一方面,由于在转换过程中删除了TypeScript接口,因此Nest无法引用它们。这很重要,因为Pipes等功能在访问变量的元型时可以实现更多可能性。

让我们创建一个CreateCatDto类:

创建create-cat.dto.ts

JS

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

它只有三个基本属性。所有这些都被标记为readonly因为我们应该总是尽量使我们的功能尽可能纯净。

此后我们可以在以下内容中使用新创建的模式CatsController

cats.controller.ts

JS

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

完整的资源样本

这是一个使用一些可用的装饰器来创建基本控制器的示例。以下控制器公开了一些访问和操作内部数据的方法。

cats.controller.ts

JS

import { Controller, Get, Post, Body, Put, Param, Delete } from '@nestjs/common'; @Controller('cats') export class CatsController { @Post() create(@Body() createCatDto) { return 'This action adds a new cat'; } @Get() findAll(@Query() query) { return `This action returns all cats (limit: ${query.limit} items)`; } @Get(':id') findOne(@Param('id') id) { return `This action returns a #${id} cat`; } @Put(':id') update(@Param('id') id, @Body() updateCatDto) { return `This action updates a #${id} cat`; } @Delete(':id') remove(@Param('id') id) { return `This action removes a #${id} cat`; } }

处理错误

有一个关于处理错误的(即有例外的工作),单独的一章。详细请看这里

启动与运行

在上面的控制器完全准备好的情况下,Nest仍然不知道CatsController存在,因此不会创建此类的实例。

控制器总是属于模块,这就是我们controllers@Module()装饰器中保存数组的原因。由于我们除了root之外没有任何其他模块ApplicationModule,我们还没有定义其他任何模块,因此我们将使用它来引入CatsController

app.module.ts

JS

import { Module } from '@nestjs/common'; import { CatsController } from './cats/cats.controller'; @Module{ controllers: [CatsController], }) export class ApplicationModule {}

我们将元数据附加到模块类,Nest现在可以轻松地反映必须安装哪些控制器。

库特定的方法

到目前为止,我们已经讨论过Nest标准的操作响应方式。操纵响应的第二种方法是使用特定于库的响应对象。为了注入特定的响应对象,我们需要使用@Res()装饰器。为了显示差异,让我们重写CatsController以下内容:

JS

import { Controller, Get, Post, Res, HttpStatus } from '@nestjs/common'; @Controller('cats') export class CatsController { @Post() create(@Res() res) { res.status(HttpStatus.CREATED).send( } @Get() findAll(@Res() res) { res.status(HttpStatus.OK).json([] } }

尽管此方法有效,并且实际上通过提供对响应对象的完全控制(标头操作,特定于库的功能等)确实在某些方面提供了更大的灵活性,但应谨慎使用。总的来说,这种方法还不太清楚,并且确实有一些缺点。主要缺点是您的代码变得依赖于平台(因为底层库在响应对象上可能具有不同的API),并且难以测试(您必须模拟响应对象等)。