type
status
date
slug
summary
tags
category
icon
password
1. 前言
同福客栈应该有一个菜单供客人点餐,每一道菜都有自己的信息(比如配料是什么、价格多少),配料和价格可以调整,可以不提供这道菜。
简而言之,菜单就是一个资源,RESTful 风格的 API 接口如下:
- GET /menus 获取所有菜品
- GET /menus/:id 获取某个菜品详情
- POST /menus 添加新的菜品
- PATCH /menus/:id 修改菜品详情
- DELETE /menus/:id 删除某个菜品
2. 快速生成 menus 资源
通过以下命令快速生成一个有关 menus 资源的控制器文件:
执行后会在 src 目录下生成一个有关 menus 资源的文件夹:
可以看到这组资源的构成与外层的 app 是类似的,都由 controller、service、module 文件构成。
先前说过“约定大于配置”,因此可以预见的是 controller 文件里应该写了 menus 相关的路由匹配以及对应执行的 service 方法,而 service 文件中包含了业务处理逻辑和具体响应给前端的内容,module 则负责如何组织它们。
3. 控制器
打开 menus.controller.ts 文件可以看到已经自动生成了相关代码,整体结构如下:
在 Nest 中使用了大量的装饰器语法,可以理解为“武器赋魔”,使之具备某种能力。
@Controller
用于将类 MenusController
标记为控制器,控制器是用于处理传入的 HTTP 请求的类。它定义了应用程序的路由,并根据不同的 HTTP 方法(如 GET
、POST
、PUT
、DELETE
等)来处理请求。@Controller('menus')
中的字符串参数 'menus'
指定了控制器处理的基础路由路径,在这个例子中,所有与这个控制器相关的路由都会以 /menus
开头。3.1 依赖注入
在控制器类 MenusController 中有如下代码:
在 Nest 中有两个很重要的概念:
- 控制反转(Inversion of Control,简称 IOC)
- 依赖注入(Dependency Injection,简称 DI)
constructor(private readonly menusService: MenusService) {}
:调用构造函数时,Nest 会通过 IOC 容器来自动实例化依赖项(这里指的是 MenusService
:一个服务类,负责处理与菜单相关的业务逻辑),并自动将 MenusService
的实例注入到这个控制器类 MenusController
中。这样一来,就能在控制类
MenusController
中通过 this.menusService
来调用 MenusService
中的实例方法了。⚠️ 注意:通过
private readonly
声明,这个依赖只能在类内部使用,并且不能被修改。依赖注入这种解耦设计不再需要将繁杂的业务逻辑写在控制器中,而是将依赖关系从内部转移到外部,这意味着可以根据需求自由添加或更换依赖项。
在这里可以说
MenusController
依赖 MenusService
,MenusService
为 MenusController
的依赖项。除了能够匹配到路由,下一步是要处理对应的 HTTP 请求(POST、GET、PATCH、DELETE)。
3.2 @Post()
@Post()
:这个装饰器标识此方法处理 POST 请求。通常用于创建新的资源。
create(@Body() createMenuDto: CreateMenuDto)
:这个方法接收一个createMenuDto
参数,表示请求体中的数据。@Body()
装饰器从 HTTP 请求的 body 中提取数据,并将其转换为CreateMenuDto
对象。从这里可以看出:类中的方法是可以使用装饰器的,甚至是方法中的入参。
this.menusService.create(createMenuDto)
:调用MenusService
实例的create
方法,将createMenuDto
传递给它。MenusService
将负责处理实际的创建逻辑。
有了这段代码,掌柜的可以添加新的菜品了:
3.3 @Get()
@Get()
:这个装饰器标识此方法处理 GET 请求。通常用于获取资源的列表。
findAll()
:直接调用MenusService
的findAll
方法来获取所有菜单。
有了这段代码,客人们可以查看所有的菜品了:
3.4 @Get(':id')
@Get(':id')
:这个装饰器标识此方法处理 GET 请求,并且路径中带有一个id
参数。:id
是一个路径参数,表示特定资源的标识符。
findOne(@Param('id') id: string)
:这个方法使用@Param('id')
从请求路径中提取id
参数,并将其转换为字符串类型传递给findOne
方法。
this.menusService.findOne(+id)
:调用MenusService
的findOne
方法,传入的id
参数前加上+
表示将字符串id
转换为数字。
有了这段代码,客人们可以查看某一道菜品的详情了:
3.5 @Patch(':id')
@Patch(':id')
:这个装饰器标识此方法处理 PATCH 请求。PATCH 请求通常用于更新部分资源。
update(@Param('id') id: string, @Body() updateMenuDto: UpdateMenuDto)
:该方法从请求路径中提取id
参数,从请求体中提取更新数据updateMenuDto
。
this.menusService.update(+id, updateMenuDto)
:调用MenusService
的update
方法,更新指定id
的菜单。
有了这段代码,掌柜的可以修改菜品信息了:
3.6 @Delete(':id')
@Delete(':id')
:这个装饰器标识此方法处理 DELETE 请求,通常用于删除资源。
remove(@Param('id') id: string)
:从请求路径中提取id
参数。
this.menusService.remove(+id)
:调用MenusService
的remove
方法,删除指定id
的菜单。
有了这段代码,掌柜的可以删除菜品信息了:
至此,可以说前言部分的 API 接口都已经完成了。
3.7 @Query()
通过 3.3 的代码就可以查看所有的菜品,但是有时候希望通过名称 name 和类型 category 去过滤菜单,这就要用到查询字符串的装饰器
@Query
,假设用户可以通过以下接口拿到过滤菜单:- GET /menus/search?name=beef&category=Chinese
@Get('search')
装饰器将会匹配以 /menus/search 开头的路由。⚠️ 注意:放在动态路由前面,防止被优先捕获。
对应的 menus.service.ts 中的
search
方法修改如下:这样就可以分别从查询字符串中拿到
name
和 category
的值,并分别赋值给对应参数,再调用 MenusService
的 search
方法,找到指定 name
和 category
范围下的数据了。现在,客人可以筛选菜品了:
4. 总结
这篇博客介绍了如何快速生成一个资源,以及控制器相关的内容。结合装饰器,控制器类能够负责处理与菜单相关的 HTTP 请求。它利用 NestJS 提供的装饰器来标识每个方法处理的请求类型(GET、POST、PATCH、DELETE)以及路径参数。其他的各种装饰器将会在以后用到时才会出现。
业务逻辑部分通过依赖注入的方式委托给
MenusService
服务类来处理。这样使得控制器更加简洁,专注于路由处理和请求解析,而将具体的业务逻辑交由服务类负责。下一篇将讲解服务类的相关内容。- 作者:Eric 见嘉
- 链接:https://tangly1024.com/article/nest-controller
- 声明:本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。