Nestjs 自翻中文文档(二)

Nestjs中文文档(二): 控制器

版本: 6.10.14

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

控制器的目的是接收应用程序的特定请求。路由机制控制哪个控制器接收哪些请求。通常,每个控制器都有多个路由,不同的路由可以执行不同的操作

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

路由

在下面的示例中,我们将使用@Controller()装饰器,这是定义基本控制器所必需的。我们将指定cats的可选路由路径前缀.在@Controller()装饰器中使用路径前缀可以让我们轻松地对一组相关路由进行分组,并将重复的代码最小化。

例如,我们可以选择对一组路由进行分组,这些路由在route/customers管理下与customers实体类的交互。在这种情况下,我们可以在@Controller()装饰器中指定路径前缀customers,这样就不必为文件中的每个路由重复该部分路径

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()装饰器, 告诉Nest为HTTP请求做特殊处理。装饰器参数对应于HTTP请求方法(本例中为GET)和路由路径。路由路径是什么?处理程序的路由路径是通过连接为控制器声明的(可选)前缀和在请求装饰器中指定的任何路径来确定的。由于我们已经为每个路由(cats)声明了一个前缀,并且没有在装饰器中添加任何路径信息,Nest将把GET/cats请求映射到此处理程序。如前所述,路径包括可选的控制器路径前缀和在请求方法的装饰器中声明的任何路径字符串。例如,customers的路径前缀与装饰了的@Get('profile')结合将生成Get/customers/profile等请求的路由映射

在上面的示例中,当该控制器发出GET请求时,Nest将请求路由到用户定义的findAll()方法。注意,我们在这里选择的方法名是完全任意的。显然,我们必须声明一个方法来绑定路由,但是Nest对所选的方法名没有任何意义。也就是说方法名与路由路径无关。

此方法将返回一个200状态代码和相关联的响应,在本例中只是一个字符串。为什么会这样?为了解释,我们将首先介绍Nest使用两种不同的选项来操纵响应的概念。

描述
Standard 使用此内置方法,当请求处理程序返回JavaScript对象或数组时,它将自动序列化为JSON。然而,当它返回一个JavaScript原语类型(例如string、number、boolean)时,Nest将只发送值而不尝试序列化它。这使得响应处理变得简单:只需返回值,Nest就可以处理其余的部分。此外,除了使用201的POST请求外,默认情况下响应的状态代码始终为200。我们可以通过在处理程序级别添加@HttpCode装饰来轻松更改此行为(请参阅状态代码)。
Library-specific 我们可以使用特定于库的(例如Express)响应对象,它可以使用方法处理程序签名中的@Res()装饰器(例如findAll(@Res()response))注入。使用这种方法,您可以(也有能力)使用该对象暴露的本机响应处理方法。例如,使用Express,可以使用类似response.status(200.send()的代码构造响应
警告: 不能同时使用这两种方法。Nest检测处理程序何时使用@Res()或@Next(),表明您选择了Library-specific的选项。如果同时使用两种方法,则此Standard选项将自动禁用,并且不再按预期工作。Nest在同时使用了类装饰器响应请求和参数装饰器请求时, 以参数装饰器为准,类装饰器将失效,这类似于就近原则

请求对象

处理程序通常需要访问客户端请求的详细信息。Nest提供对底层平台的请求对象的访问(默认情况下是Express)。我们可以通过将@Req()装饰器添加到处理参数中来指示Nest注入请求对象,从而访问该请求对象

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头和正文的属性。在大多数情况下,不需要手动获取这些属性。我们可以使用专用的装饰器,例如@Body()或@Query(),它们是现成的。下面是提供的装饰器和它们表示的纯平台特定对象的列表。

装饰器 对应对象
@Request() 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

为了与底层HTTP平台(如Express和fastfy)上的类型兼容,Nest提供了@Res()和@Response()装饰符。@Res()只是@Response()的别名。两者都直接暴露底层的本机平台响应对象接口。使用它们时,还应导入底层库的类型(例如,@types/express)以充分利用它们。注意,当您在方法处理程序中注入@Res()或@Response()时,您会将Nest放入该处理程序的特定于库的模式中,并负责管理响应。执行此操作时,必须通过调用response对象(例如res.json(…)或res.send(…)来发出某种响应,否则HTTP服务器将挂起

提示: 您是否想学习如何制作自己的装饰器呢(后面将会学习到,暂未翻译)前往浏览 https://docs.nestjs.com/custom-decorators

资源

之前我们使用了@Get装饰器来通过get方法访问资源,现在我们来试试用post来访问:

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请求装饰器-@Put()、@Delete()、@Patch()、@Options()、@Head()和@All()。每个表示其各自的HTTP请求方法。

路由通配符

也支持通配符模式,例如,星号用作通配符,将匹配任何字符组合。

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

这个ab*cd将会匹配abcd,ab_cd,abecd等等,?,+,*, 和()也允许在路由路径中。

状态码

如前所述,默认情况下响应状态代码始终为200,除了post为201外。我们可以很轻松的添加@HttpCode装饰器来改变默认行为

@Post()
@HttpCode(204)
create() {
  return 'This action adds a new cat';
}
先得导包: import HttpCode from @nestjs/common

头信息

要指定自定义响应头,可以使用@header()装饰器或库特定的响应对象(并直接调用res.header())

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

重定向

想要重定向到某一个URL,你可以使用@Redirect()装饰器,也可以直接使用特定的响应对象如:res.redirect()

@Get()
@Redirect('https://nestjs.com', 301)

有时候你需要动态的传递参数,你只需要返回的时候遵循如下结构:

{
  "url": string,
  "statusCode": number
}

返回的值将重写传递给@Redirect()装饰器的任何参数。例如:

@Get('docs')
@Redirect('https://docs.nestjs.com', 302)
getDocs(@Query('version') version) {
  if (version && version === '5') {
    return { url: 'https://docs.nestjs.com/v5/' };
  }
}

路由参数

当需要接受动态数据作为请求的一部分时(例如,get/cats/1以获取id为1的cat),具有静态路径的路由将不起作用。为了用参数定义路由,我们可以在路由路径中添加路由参数标记,以捕获请求URL中该位置的动态值。下面@Get()decorator示例中的route参数标记演示了这种用法。以这种方式声明的路由参数可以使用@Param()装饰器访问,该装饰器应该添加到方法相应地方。

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

@Param()用于修饰方法参数(上面示例中的params),并使route参数作为该修饰方法参数在方法主体中的属性可用。如上面的代码所示,我们可以通过引用params.id来访问id参数,也可以将特定的参数标记传递给装饰器,然后在方法体中直接通过名称引用route参数。

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

子域路由

@Controller decorator可以采用host选项,要求传入请求的HTTP host与某些特定值匹配。

@Controller({ host: 'admin.example.com' })
export class AdminController {
  @Get()
  index(): string {
    return 'Admin page';
  }
}
警告: 由于fastfy缺乏对嵌套路由器的支持,因此在使用子域路由时,应改用(默认)Express适配器。

与路由路径类似,hosts选项可以使用令牌捕获host名中该位置的动态值。下面@Controller()装饰器示例中的主机参数标记演示了这种用法。可以使用@HostParam()装饰器访问以这种方式声明的主机参数,该装饰器应添加到方法签名中。

@Controller({ host: ':account.example.com' })
export class AccountController {
  @Get()
  getInfo(@HostParam('account') account: string) {
    return account;
  }
}

异步性

我们喜欢现代JavaScript,而且我们知道数据提取主要是异步的。这就是为什么Nest支持异步函数并能很好地工作

每个异步函数都必须返回一个承诺。这意味着您可以返回一个Nest能够自行解析的值。让我们看看这个例子.

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

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

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

以上两种方法都有效,您可以使用任何符合您要求的方法

请求的有效载荷

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

但是首先(如果您使用TypeScript),我们需要确定DTO(数据传输对象)架构。DTO是定义如何在网络上发送数据的对象。我们可以通过使用TypeScript接口或简单的类来确定DTO模式。有趣的是,我们建议在这里使用。为什么?类是JavaScript ES6标准的一部分,因此,它们在编译的JavaScript中保留为真实实体。由于TypeScript接口在编译过程中被移除,Nest不能在运行时引用它们,这一点很重要,因为管道(pipe)等功能在运行时可以访问变量的元类型.

让我们创建CreateCatDto

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

它只有三个基本属性。之后,我们可以在内使用新创建的DTO CatsController

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

本小节全部的demo

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

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仍然不知道CatsController该类是否存在,因此将不会创建此类的实例.

控制器始终属于一个模块,这就是为什么我们controllers@Module()装饰器中包含该数组的原因。由于除了root之外AppModule,我们还没有定义其他任何模块,因此我们将使用它来引入CatsController

import { Module } from '@nestjs/common';
import { CatsController } from './cats/cats.controller';

@Module({
  controllers: [CatsController],
})
export class AppModule {}

我们使用@Module()装饰器将元数据附加到模块类,Nest现在可以轻松反映必须安装的控制器。

附:使用lib特性的做法

到目前为止,我们已经讨论了操作响应的Nest标准方法。处理响应的第二种方法是使用特定于库(express)的响应对象

为了注入一个特定的响应对象,我们需要使用@Res()装饰器,如下(可以比较下和Nest标准处理方法的不同点)

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([]);
  }
}

虽然这种方法是有效的,并且事实上通过提供对响应对象(头部操作、库特定特征等)的完全控制在某些方面允许更多的灵活性,但是它应该谨慎使用。

总的来说,这种方法不太清晰,而且确实有一些缺点。主要缺点是与依赖于Nest标准响应处理的特性(如拦截器和@HttpCode()装饰器)不兼容。此外,您的代码可能依赖于平台(因为底层库在响应对象上可能有不同的api < 前面提到过的express和fastify>),并且很难测试(您必须模拟响应对象等)。

因此,在可能的情况下,应始终首选Nest标准方法。

阅读 788

推荐阅读
chendm的专栏
用户专栏

9 人关注
26 篇文章
专栏主页