Nest 控制器阅读 NestJS 中文文档和神光的 Nest 通关秘籍后的学习收获。 Nest 中的控制器作用就是负责

Nest 中的控制器作用就是负责处理传入的请求并向客户端返回响应
。。嗯,如果要说从前端的角度来讲,控制器就是路由,路由匹配与路由展示。
为什么会这么说呢?因为都是按照输入、匹配、输出的执行顺序。
细想一下前端路由,当我们输入一个 url 地址之后,就会进入代码程序中,去进行路由匹配(Switch 组件),当匹配成功之后,就进行路由展示(Route 组件)。
而对于控制器来说也是一样,当客户端请求一个 api 路径,进入服务代码,也会进行路由匹配,当路由匹配成功之后,就返回对应的结果给客户端。
前端路由在前端中非常的重要,那么控制器在 Nest 也是非常的重要。
控制器基本认识
定义一个基本的控制器,需要使用 @Controller
来进行装饰。
import { Controller } from "@nestjs/common";
@Controller()
export class XxxController {} // XxxController 就是一个控制器
在 nest 程序中控制器会存在多个,都会被路由机制
所管控。路由机制控制哪个控制器接收哪些请求。
而每个控制器又存在多个路由,不同的路由可以执行不同的操作。路由匹配时,是从上到下的顺序依次匹配。

上图是 XxxController
控制器下定义的三个路由,2 个 GET 请求,1 个 POST 请求。当客户端请求不同路径的时候,就会返回不同的内容。
仔细观察,上面的代码可以被优化的,/xxx
写的过于频繁,每个路由都在写。那么有什么办法可以解决呢?
其实每个路由都写上 /xxx
,说明了它们是被设计者归为同一组的,而控制器就是以组来进行划分的。那么就可以借助 @Controller
装饰器来解决。
@Controller("/xxx")
export class XxxController {}
当上面这样写之后,每个路由都不需要在写/xxx
了。当继续接口请求时,nest 内部会自动请求映射到该控制器并进行路由匹配,这样就减少了代码的重复编写。
这也就是中文网所说的,,路由路径包括可选的控制器路径前缀
和请求方法装饰器中声明的任何路径字符串
。
再来看一个现象(使用 Apifox 进行模拟):
当使用 GET 请求 /xxx/list
时,会发现返回的数据是 动态路由
。从前面看, 应该返回的字符串是 get list
,为什么会造成这样的结果呢?
其实在上面就已经说了,路由匹配是从上到下的顺序,而不是精确匹配路由。
当我们动态路由获取的 id 进行返回时,就会发现:
所以说,在我们在设计程序路由时,还要留一个心思来考虑路由匹配的顺序,保证程序执行是可预料的。
控制器请求响应
控制器负责处理传入的请求并向客户端返回响应。
- 是如何接受客户端的请求?
- 是如何响应客户端?
请求方式
在 nestjs/common
中暴露出了几种请求方式的装饰器
import { Get, Post, Put, Delete, Patch, Head } from "@nestjs/common";
一般来说,常用的请求方式就只有四种:get、post、put、delete,遵循 restful 接口风格规范。
而且还有很多公司服务端接口就只有两种请求方式:get 和 post。
这是装饰器都支持可选参数。可选参数作为请求路径的一部分。
@Get(':id') // 动态参数
五种数据传输
第一种:url params
http://localhost:3000/list/12321
把参数写着 url 上, 这里的 12321 就是路径中的参数(url param),获取时直接从路径上读取。
第二种:url query
也是把参数写 url 上,只不过是通过 url 中 ?
后面的用 &
分隔的字符串传递数据。如果是非英文的字符和一些特殊字符的情况,最好还要使用 encodeURIComponent 编码一下。
`http://localhost:3000?name=${encodeURIComponent("向")}&age=19`;
第三种:form-urlencoded
form 表单的提交方式。与 query 的方式对比:
- 相同点:其格式都是一样,
&
拼接成的字符串,一些特殊的字符需要进行编码 - 不同点:存在位置不一样,query 是存放在 url 路径上,而 form-urlencoded 是存在 body 中
也必须设置content-type
类型为application/x-www-form-urlencoded
第四种:form-data
form data 不再是通过 &
分隔数据,而是使用 boundary
作为边界,进行数据分割。 boundary
组成为 -----字符串分隔符
。
类似这种:

form-data 的 Content-Type 也需要设置为 multipart/form-data
。
第五种:json
post 最常用的方式,把 json 对象保存在 body 中,服务端直接从 body 中获取。
Content-Type 的值为 application/json
,也是默认值。
请求对象和响应对象
针对请求对象,Nest 内部提供了 @Request()
或者@Req()
来直接获取 request 对象,来获取里面的 query 对象、params 对象、body 对象等等。也还提供了 @Query()
、@Param()
、@Body()
等装饰器直接获取各自的对象。
// 方式一: request
import { Request } from "@nestjs/common";
@Controller("xxx")
export class XxxController {
@Get("query")
getQuery(@Request() request) {
console.log(request);
console.log(request.query); // query 对象
console.log(request.body); // body 对象
console.log(request.params); // params 对象
return request.query;
}
}
// 方式二:直接获取各自的对象
import { Controller, Get, Param, Query, Body } from "@nestjs/common";
@Controller("xxx")
export class XxxController {
@Get("query")
getQuery(@Query() query, @Body() body, @Param() params) {
console.log(query);
console.log(body);
console.log(params);
return query;
}
}
针对上面两种,还是推荐使用方式二,直接获取各自的对象。
针对响应,Nest 内部提供了两种方式: nest 标准
或 指定库(express,fastify)
。
指定库, response 对象就可以通过 @Response
或者 @Res
方式来进行获取,然后执行 response.send()
的形式进行响应。但是官方还是推荐使用 nest 标准
,采用内置方法。
使用这个内置方法,当请求处理程序返回一个 JavaScript 对象或数组时,它将 自动地 序列化为 JSON。 然而,当它返回一个 JavaScript 原始类型(例如,string
, number
, boolean
)时,Nest 将只发送值而不尝试序列化它。 这使得响应处理变得简单: 只需返回值,Nest 会处理剩下的事情。
代码实操
针对上面的五种数据传输,采用请求方式,获取请求对象及响应处理。
- 使用 axios 来进行网络请求的发送
- 使用 nest 服务端来进行响应
针对这里的跨域问题,就直接服务端配置一下即可,就没采用前端的解决跨域方式。
app.enableCors(); // 允许跨域
实操一:query 方法
客户端代码
const query = async function () {
const res = await axios.get(
"http://localhost:3000/xxx/query?name=copyer&age=18"
);
console.log(res.data);
};
直接在路径上拼接 name 和 age 属性。
服务端代码
import { Controller, Get, Request, Query } from "@nestjs/common";
@Controller("xxx")
export class XxxController {
@Get("query")
getQuery(@Query() query) {
return query;
}
}
也可以直接通过 @Query()
装饰器,获取 query 对象。也可以确切的传递一个属性 @Query('name')
,就可以直接获取 query 对象中的 name 属性。其实装饰器底层使用的原理就是读取 request 的 query 对象。
实操二:param 方法
客户端代码
const params = async function () {
const res = await axios.get("http://localhost:3000/xxx/params/111");
console.log(res.data);
};
111 就是路径上的 params 拼接
服务端代码
import { Controller, Get, Param } from "@nestjs/common";
@Controller("xxx")
export class XxxController {
@Get("params/:id")
getParam(@Param("id") id) {
return id; // 没有进过序列化
}
}
实操三:form-urlencoded
客户端代码
const formUrlencoded = async function () {
const res = await axios.post(
"http://localhost:3000/xxx/formurlencoded",
`name=${encodeURIComponent("向")}&age=18`,
{
headers: { "content-type": "application/x-www-form-urlencoded" },
}
);
console.log(res.data);
};
- 汉字需要编码
- 设置 Content-Type 为
application/x-www-form-urlencoded
服务端代码
import { Controller, Post, Body, HttpCode } from "@nestjs/common";
@Controller("xxx")
export class XxxController {
@HttpCode(200)
@Post("formurlencoded")
getFormurlencoded(@Body() body) {
// 这里 body 类型定义应该使用 DTO
return body;
}
}
@Body()
来获取 body 的参数。- post 请求默认返回的状态码为 201,可以通过
@HttpCode(200)
可以设置状态码为 200。
方式四:json
客户端代码
const json1 = async function () {
const res = await axios.post("http://localhost:3000/xxx/json1", {
name: "向",
age: 12,
});
console.log(res.data);
};
服务端代码
import { Controller, Post, Body, HttpCode } from "@nestjs/common";
@Controller("xxx")
export class XxxController {
@HttpCode(200)
@Post("json1")
getJson(@Body() body) {
// 这里 body 类型定义应该使用 DTO
return body;
}
}
方式五:form-data
客服端代码
<body>
<input type="file" id="fileInput" multiple />
<script>
async function formData() {
const data = new FormData();
data.set("name", "向");
data.set("age", 18);
data.set("file1", fileInput.files[0]);
const res = await axios.post("http://localhost:3000/xxx/files", data, {
headers: { "content-type": "multipart/form-data" },
});
console.log(res);
}
fileInput.onchange = formData;
</script>
</body>
- 传递 form-data 对象
- 设置 Content-Type 为
multipart/form-data
服务端代码
import {
Controller,
Post,
Body,
HttpCode,
UseInterceptors,
UploadedFiles,
} from "@nestjs/common";
@Controller("xxx")
export class XxxController {
@HttpCode(200)
@Post("files")
@UseInterceptors(
AnyFilesInterceptor({
// 文件上传到 uploads 文件夹下面
dest: "uploads/",
})
)
file(
@Body() body: FileDto,
@UploadedFiles() files: Array<Express.Multer.File>
) {
console.log(body, files);
return body;
}
}
- 对于文件上传,使用拦截器,把文件上传到静态资源文件下(uploads)
@UploadedFiles()
是 files 对象的装饰器Multer
类型定义需要安装pnpm add -D @types/multer
- 就可以拿去 body 对象和 files 对象。
控制器其他知识
在控制器的请求和响应的过程中,还会穿插一些额外小知识。
请求方式
在 Nest 内部中,还提供了一个装饰器 @All()
,可以匹配所有的请求方式。
路由时从上到下匹配的,/xxx/query
就会匹配上 GET 的请求方式。但是如果 GET 方式没有匹配上,就会进入 All 路由里面去。
路由通配符
所谓的通配符都是正则中的 *
。
模式匹配的路由也是支持的。例如,星号(asterisk)用作通配符,将匹配任何字符组合。
@Get('ab*cd')
findAll() {
return 'This route uses a wildcard';
}
由路径'ab*cd'
将匹配abcd
、ab_cd
、abecd
等。字符?
、+
、*
和()
可以在路由路径中使用,并且是其正则表达式对应项的子集。连字符(-
)和句点(.
)在基于字符串的路径中会被直接解释。
头部信息
使用@Header()
装饰器,来自定义头部信息。
@Post()
@Header('Cache-Control', 'none') // 设置没有缓存
create() {
return 'This action adds a new cat';
}
重定向
可以使用@Redirect()
装饰器,将响应重定向到特定的 URL;也可以使用特定于库的响应对象(并直接调用res.redirect()
)。
@Redirect()
接受两个参数,url
和statusCode
,两者都是可选的。如果省略statusCode
,其默认值为302
(Found
)。
import { Redirect } from '@nestjs/common'
@Get('query')
@Redirect('http://localhost:4000', 302) // 当请求该地址时,会转向 4000 的服务器
getQuery(@Query() query) {
console.log('get');
return query;
}
存在重定向的地址是动态的,那么 只需要返回如下格式, Nest 内部会帮你进行处理(前提该路由上使用 @Redirect()
)
{
url: 'xxx',
statusCode: 302
}
例如:
@Get('query')
@Redirect('http://localhost:4000', 302)
getQuery(@Query('version') version) {
if (version && version === '5') {
return { url: 'http://localhost:5000' };
}
}
异步性
在 Nest 路由中支持返回一个 promise,返回一个延迟值。nest 内部会自动去解析。
export class XxxController {
@Get("query")
async getQuery() {
return new Promise((resolve) => {
setTimeout(() => {
resolve("copyer");
}, 1000);
});
}
}
效果:
请求负载
通过 DTO
(数据传输对象)来定义了数据将如何通过网络发送的对象。推荐使用类来定义 DTO。(ES6 class 会保留实体,而 interface 在编译过程中,会被删除,nest 在运行时无法使用。在某些情景下,运行时会存现错误的可能,保留实体比较重要)。
在前面也说过,POST 请求中 body 的类型会通过 DTO 来进行定义
class FormUrlEncodedDto {
name: string;
age: number;
}
总结
- 控制器负责处理传入的请求并向客户端返回响应。
- 定义控制器使用
@Controller()
。每个控制器下面又会存在多个路由,路由的配置顺序从上到下的匹配。 - 请求方式常用的有 get、post、put、delete,分别对应着
@Get()
、@Post()
、@Put()
、@Delete()
来定义。 - 在前后端对接中,数据传输一般有五种方式:param、query、json、form-urlencoded、form-data。传递的方式?保存的位置?以及 content-type 值的设置
- 请求对象可以通过
@Request()
、@Query()
、@Param()
、@Body()
来分别获取对应的对象。 - 响应对象有两种方式,nest 标准(推荐方式)和 库指定。
- 针对文件上传,需要使用到
AnyFilesInterceptor
拦截器,把文件保存在服务端中。也可以通过@UploadedFiles()
获取 file 对象。 - 还有一些额外的特定知识,请求方式通匹配,路径通匹配,设置头部信息,重定向,函数的异步性,以及请求负载(DTO)。
转载自:https://juejin.cn/post/7370170468781277199