前言
上篇文章《Nest 核心概念:模块》中,我们介绍了 Nest 的模块,它是组织 Nest 应用的基本结构。Nest 应用接收请求,交给控制器处理请求,控制器中又调用服务,完成具体的业务逻辑,最后将结果响应给客户端。
本文会详细介绍控制器的用法,其实就是通过各类装饰器处理不同的请求和传参。
控制器
控制器负责处理传入的请求,并向客户端返回响应。所发挥的作用就类似于 Express 或者 Koa 中的路由中间件。
代码实现上,控制器就是使用 @Controller
装饰的类。看下它的类型声明:
export declare function Controller(prefix: string | string[]): ClassDecorator;
Controller
是一个装饰器工厂,接收一个 prefix
前缀参数,类型是字符串或者字符串数组,表示模块的路由的公共前缀,返回一个类装饰器 ClassDecorator
。
接收请求
Nest 为所有标准的 HTTP 方法提供了相应的装饰器:@Get()
、@Post()
、@Put()
、@Delete()
、@Patch()
、@Options()
、以及 @Head()
。还提供了一个特殊的装饰器@All()
,用于定义一个用于处理所有 HTTP 请求方法的处理程序。这些装饰器都从 @nestjs/common
包中导出。
下面是使用 nest generate resource user
命令生成的一个 CRUD 的例子:
import {
Controller,Get,Post,Body,Patch,Param,Delete,
} from '@nestjs/common';
import {
UserService } from './user.service';
import {
CreateUserDto } from './dto/create-user.dto';
import {
UpdateUserDto } from './dto/update-user.dto';
// 将类声明为控制器,接收的参数可作为公共路由前缀,可和方法装饰器的参数进行拼接
@Controller('user')
export class UserController {
constructor(private readonly userService: UserService) {
}
// 处理 Get 请求
@Get()
findAll() {
return this.userService.findAll();
}
// 处理 Post 请求
@Post()
create(@Body() createUserDto: CreateUserDto) {
return this.userService.create(createUserDto);
}
// 处理 Patch 请求
@Patch(':id')
update(@Param('id') id: string, @Body() updateUserDto: UpdateUserDto) {
return this.userService.update(+id, updateUserDto);
}
// 处理 Delete 请求
@Delete(':id')
remove(@Param('id') id: string) {
return this.userService.remove(+id);
}
}
示例中还有一些其他装饰器,下面接着讲。
处理请求参数
客户端向服务端传参有三种方式:
- query 参数:通过 URL 的查询参数传递,形如
/users/list?page=1
- param 参数:参数放在 URL 的 path 部分传递,形如
/users/1
, 1 可以表示 id 参数为 1 - body 参数:将数据放到请求体中传递
其中服务端要接收到 param 参数,需要预先在定义路由和路径时使用元数据定义好,这也叫动态路由,类似于 Koa 中的动态路由:
app.get('/user/:id', async (ctx, next) => {
})
或者前端路由中的动态路由,比如 vue-router 的一段配置:
const route = [
{
path: '/user/:id',
component: User
}
]
Nest 控制器定义动态路由的方式如下:
@Controller(‘user’)
class UserController {
@Get(':id')
findOne(@Param('id') id: string) {
return this.userService.findOne(+id);
}
}
拼接过后的动态路由就是 /user/:id
。
Nest 提供了 Query
、Param
、Body
三种装饰器,来获取这三类参数。它们属于参数装饰器,在方法的参数中使用。
@Get()
findAll(@Query() query) {
console.log(query);
return this.userService.findAll();
}
@Get(':id')
findOne(@Param() param) {
console.log(param);
return this.userService.findOne(+param.id);
}
使用 Postman 分别量给请求到 Nest:
Get /user?page=1
Get /user/10
命令行窗口中会打印出:
还可以通过参数装饰器的参数,直接获取具体的参数:
@Get()
findAll(@Query('page') page) {
console.log(page);
return this.userService.findAll();
}
@Get(':id')
findOne(@Param('id') id) {
console.log(id);
return this.userService.findOne(+param.id);
}
命令行窗口中打印出:
对于 body 参数,可以使用 @Body()
装饰器:
@Post()
create(@Body() createUserDto: CreateUserDto) {
console.log(createUserDto);
return this.userService.create(createUserDto);
}
使用 Postman 发送一个 Post 请求:
命令行窗口中打印:
状态码
针对常用的 HTTP 请求,比如 Get,Post,Put,Patch,Delete,只有 Post 的响应状态码是 201,其他的都是 200。这是 Nest 默认处理的。实际场景中状态码通常需要结合具体的请求处理结果进行设置,比如用户的 token 失效了,此时要设置 401
,可以使用 @HttpCode()
装饰器:
import {
HttpCode } from '@nestjs/common';
@Get()
@HttpCode(401)
getList() {
return '401';
}
@Headers() 和 @Header() 装饰器
@Headers()
是参数装饰器,用于在一个请求处理中获取需要的请求头信息。
@Header()
是方法装饰器,用于在一个请求中设置响应头信息。
import {
Controller, Post, Headers, Header } from '@nestjs/common';
import {
UserService } from './user.service';
@Controller('user')
export class UserController {
constructor(private readonly userService: UserService) {
}
@Post()
// 设置响应头
@Header('user', 'kw')
// 获取请求头
create(@Headers('Content-Type') contentType: string) {
console.log(contentType);
return 'nest controller'
}
}
在 Postman 中发送一个 Post 请求,传递的是 JSON 格式的数据,则 Postman 自动处理 Content-Type
为 application/json
:
服务端打印的信息是:
Postman 得到的响应头中有我们在控制器中自定义的 user
头:
使用底层框架的原生 API
虽然 Nest 提供了一些处理请求相关的装饰器,但是还不够细致。有时候需要获取更详细的客户端请求的细节,Nest 也考虑到这一点,提供了参数装饰器 @Request()
,别名 @Req
,表示底层框架的原生请求对象,与之对应的还有 @Response()
/ @Req()
装饰器,表示底层框架的原生响应对象。如果你熟悉 Express,就可以在 Nest 中使用 Express 的 req
和 res
对象了。比如在一个请求中,从请求头中解析 Authorization
Header,除了使用 @Headers()
装饰器以外,还可以使用 @Req()
装饰器:
import {
Controller, Get, Req } from '@nestjs/common';
import {
AppService } from './app.service';
// 导入 Request ,作为 req 对象的类型使用
import {
Request } from 'express';
@Controller()
export class AppController {
@Get()
getList(@Req() req: Request) {
const authorization = req.headers.authorization;
return authorization;
}
}
使用 Postman 发送一个带 Authorization
Header 的请求,控制器可以正确解析出 Header 并响应给客户端:
两种响应方式
控制器响应数据给客户端有两种方式。一种是默认的标准方式,也就是在控制器的成员方法中直接 return
。此时 Nest 框架会自动处理返回的数据。当返回一个 JavaScript
对象或者数组时,Nest 会自动序列化为 JSON
对象。当返回一个 JavaScript
基本类型(例如string、number、boolean
)时, Nest 将只发送值,而不尝试序列化它。
还有一种响应方式就是使用底层框架的响应对象,例如使用 Express 的原生 res
对象,先通过 @Res()
装饰器取得原生响应对象,导入 express
包中的 Response
作为响应对象的类型:
import {
@Res } from '@nestjs/common';
import {
Response } from 'express';
@Controller()
export class AppController {
@Get()
getHello(@Res() res: Response) {
res.send('hello')
}
}
这种方式能提供很大的灵活性,但也存在缺点,一是代码脱离了 Nest 的抽象,去依赖了底层的框架,Express 和 Fastify 在做响应时可能具有不同的 API。如果之后将 Express 更换为 Fastify,可能需要回过头来修改代码。二是如果 Nest 检测到在方法中注入了 @Req()
,将不能再使用标准方式直接 return
响应,必须通过 res
去设置。如果依然想使用 Nest 框架的能力,可以给 @Req()
传入一个元数据对象进行设置:
import {
@Res } from '@nestjs/common';
import {
Response } from 'express';
@Controller()
export class AppController {
@Get()
getHello(@Res({
passthrough: true }) res: Response) {
res.append('User', 'Kunwu')
return 'hello'
}
}
重定向
要做请求的重定向,可以使用 @Redirect()
装饰器,它接收两个可选参数,url
和 statusCode
用于设置重定向的 URL 和 状态码。重定向状态码默认为 302
。或者使用上面介绍的使用底层框架的 res
对象(res.redirect()
)。
使用装饰器重定向:
@Get('docs')
@Redirect('https://docs.nestjs.com')
getDocs() {
return;
}
如果需要动态进行重定向,在使用了 @Redirect()
之后,控制器方法返回装饰器的参数对象:
@Get('docs')
@Redirect()
getDocs(@Query('version') version) {
if (version && version === '8') {
return {
url: 'https://docs.nestjs.com/v8/'
};
}
}
最后
关于控制器,本文介绍了用到比较多的特性,包括:
- 接收特定请求
- 处理请求参数
- 设置状态码,解析请求头,设置响应头
- 使用底层框架的原生请求对象和响应对象
- 重定向
在文档中还有一些其他用法的介绍,比如 @Session()
装饰器,异常处理等,大家可以去阅读文档,或者等以后用到的时候再去查阅。
感谢阅读!