控制器

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

图1

为了创建一个基本的控制器,我们必须使用装饰器。多亏了他们,Nest 知道如何将控制器映射到相应的路由。

cats.controller.ts

  1. import { Controller, Get } from '@nestjs/common';
  2. @Controller('cats')
  3. export class CatsController {
  4. @Get()
  5. findAll() {
  6. return [];
  7. }
  8. }

装饰器

在上面的例子中,我们使用了定义基本控制器所需的 @Controller('cats') 装饰器 。 这个装饰器是强制性的。 cats 是每个在类中注册的每个路由的可选前缀。使用前缀可以避免在所有路由共享通用前缀时出现冲突的情况, findAll() 方法附近的 @Get() 修饰符告诉 Nest 创建此路由路径的端点,并将每个相应的请求映射到此处理程序。由于我们为每个路由(cats)声明了前缀,所以 Nest 会在这里映射每个 /cats 的 GET 请求。

当客户端调用此端点时, Nest 将返回 200 状态码和解析的 JSON, 因此在本例中-它只是一个空数组。这怎么可能?

两种可能的方法来处理响应:

标准(推荐) 我们对处理程序的处理方式与普通函数相同。当我们返回 JavaScript 对象或数组时, 它会自动转换为 JSON。当我们返回字符串, Nest 将只发送一个字符串而不尝试解析它。
此外, 响应状态代码在默认情况下总是 200, 除了 POST 请求外,此时它是 201。我们可以通过在处理程序层添加 @HttpCode(...) 装饰器来轻松地更改此行为。
库专用 我们可以使用库特定的响应对象,,例如findAll(@Res()response)。

我们可以使用 「express」 响应对象, 我们可以在函数签名中使用 @Res() 装饰器注入, 例如 findAll (@Res() response)

!> 注意! 禁止同时使用这两种方法。 Nest 检测处理程序是否正在使用 @Res()@Next(),如果是这样,此单个路由的「标准」方式将被禁用。

Request 对象

许多端点需要访问客户端的请求细节。 实际上,Nest 正使用 特定的库 请求对象。 我们可以强制 Nest 使用 @Req() 装饰器将请求对象注入处理程序。

cats.controller.ts

  1. import { Controller, Get, Req } from '@nestjs/common';
  2. @Controller('cats')
  3. export class CatsController {
  4. @Get()
  5. findAll(@Req() request) {
  6. return [];
  7. }
  8. }

「Request」对象表示 HTTP 请求,并具有「Request」查询字符串,参数,HTTP 标头 和 正文的属性(在这里阅读更多),但在大多数情况下, 不必手动获取它们。 我们可以使用专用的装饰器,比如 @Body()或 可以直接使用的装饰器 @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]

更多端点

我们已经创建了一个端点来获取数据(GET 路由)。 提供创建新记录的方式也是很好的。让我们创建 POST 处理程序:

cats.controller.ts

  1. import { Controller, Get, Post } from '@nestjs/common';
  2. @Controller('cats')
  3. export class CatsController {
  4. @Post()
  5. create() {}
  6. @Get()
  7. findAll() {
  8. return [];
  9. }
  10. }

这很容易。 Nest 以同样的方式提供了其他的端点装饰器 - @Put()@Delete()@Patch()@Options()@Head()@All()

状态码操作

如前面所说,默认情况下,响应的状态码总是200,除了 POST 请求外,此时它是201,我们可以通过在处理程序层添加@HttpCode(...) 装饰器来轻松更改此行为。

cats.controller.ts

  1. import { Controller, Get, Post, HttpCode } from '@nestjs/common';
  2. @Controller('cats')
  3. export class CatsController {
  4. @HttpCode(204)
  5. @Post()
  6. create() {}
  7. @Get()
  8. findAll() {
  9. return [];
  10. }
  11. }

路由参数

当需要将动态数据作为 URL 的一部分接受时, 使用静态路径的路由无法提供帮助。要使用路由参数定义路由,只需在路由的路径中指定路由参数,如下所示。

  1. @Get(':id')
  2. findOne(@Param() params) {
  3. console.log(params.id);
  4. return {};
  5. }

Async / await

我们喜欢现代 JavaScript,而且我们知道数据读取大多是异步的。 这就是为什么 Nest 支持异步函数,并且与他们一起工作得非常好。

!> 了解更多关于 Async / await 请点击这里

每个异步函数都必须返回 Promise。这意味着您可以返回延迟值, 而 Nest 将自行解析它。让我们看看下面的例子:

cats.controller.ts

  1. @Get()
  2. async findAll(): Promise<any[]> {
  3. return [];
  4. }

这是完全有效的。

此外, Nest 路由处理程序更强大。它可以返回一个 Rxjs observable 的流,使得在简单的 web 应用程序和 微服务 之间的迁移更加容易。

cats.controller.ts

  1. @Get()
  2. findAll(): Observable<any[]> {
  3. return of([]);
  4. }

没有推荐的方法。 你可以使用任何你想要的。

POST 处理程序

奇怪的是, 这个 POST 路由处理程序不接受任何客户端参数。我们至少应该在 @Body() 这里添加参数来解决这个问题。

首先, 我们需要确定 DTO (数据传输对象) 架构。DTO 是一个定义如何通过网络发送数据的对象。我们可以使用 TypeScript 接口或简单的类来完成。令人惊讶的是,我们建议在这里使用类。为什么?这些类是 JavaScript ES6 标准的一部分, 所以它们只是简单的函数。另一方面, TypeScript 接口在编译过程中被删除, Nest 不能引用它们。这一点很重要, 因为管道等特性能够在访问变量的元类型时提供更多的可能性。

我们来创建 CreateCatDto

create-cat.dto.ts

  1. export class CreateCatDto {
  2. readonly name: string;
  3. readonly age: number;
  4. readonly breed: string;
  5. }

它只有三个基本属性。 所有这些都被标记为只读,因为我们应该尽可能使我们的功能尽可能不被污染。

现在我们可以使用 CatsController 中的模式:

cats.controller.ts

  1. @Post()
  2. async create(@Body() createCatDto: CreateCatDto) {}

处理 errors

这里有一个关于处理异常的独立章节

最后一步

控制器已经准备就绪,可以使用,但是 Nest 不知道 CatsController 是否存在,所以它不会创建这个类的一个实例。 我们需要告诉它。

控制器总是属于模块,这就是为什么我们 controllers 在 @Module() 装饰器中保存数组。 由于除了根 ApplicationModule,我们没有其他模块,现在就让我们使用它:

app.module.ts

  1. import { Module } from '@nestjs/common';
  2. import { CatsController } from './cats/cats.controller';
  3. @Module({
  4. controllers: [CatsController],
  5. })
  6. export class ApplicationModule {}

!> 我们将元数据附加到 module 类,所以现在 Nest 可以很容易地反映出哪些控制器必须被安装。

特定库 方式

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

  1. import { Controller, Get, Post, Res, Body, HttpStatus } from '@nestjs/common';
  2. import { CreateCatDto } from './dto/create-cat.dto';
  3. @Controller('cats')
  4. export class CatsController {
  5. @Post()
  6. create(@Res() res, @Body() createCatDto: CreateCatDto) {
  7. res.status(HttpStatus.CREATED).send();
  8. }
  9. @Get()
  10. findAll(@Res() res) {
  11. res.status(HttpStatus.OK).json([]);
  12. }
  13. }

从我的角度来看,这种方式是非常不清晰。 我当然更喜欢第一种方法,但为了使 Nest 向下兼容以前的版本,这种方法仍然可用。 而且,响应对象提供了更大的灵活性 - 您可以完全控制响应。