### 控制器
控制器负责处理传入的**请求**并将**响应**返回给客户端。
![](https://docs.nestjs.com/assets/Controllers_1.png)
控制器的目的是接收应用程序的特定请求。**路由**机制控制哪个控制器接收哪个请求。很多时候,每个控制器都有多个路由,不同的路由可以执行不同的动作。
为了创建一个基本的控制器,我们使用类和**装饰器**。装饰器将类与所需的元数据相关联,并使 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?