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 资源的文件夹:
menus 资源
menus 资源
可以看到这组资源的构成与外层的 app 是类似的,都由 controller、service、module 文件构成。
先前说过“约定大于配置”,因此可以预见的是 controller 文件里应该写了 menus 相关的路由匹配以及对应执行的 service 方法,而 service 文件中包含了业务处理逻辑和具体响应给前端的内容,module 则负责如何组织它们。

3. 控制器

打开 menus.controller.ts 文件可以看到已经自动生成了相关代码,整体结构如下:
在 Nest 中使用了大量的装饰器语法,可以理解为“武器赋魔”,使之具备某种能力
@Controller 用于将类 MenusController 标记为控制器,控制器是用于处理传入的 HTTP 请求的类。它定义了应用程序的路由,并根据不同的 HTTP 方法(如 GETPOSTPUTDELETE 等)来处理请求。
@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 依赖 MenusServiceMenusServiceMenusController 的依赖项。
除了能够匹配到路由,下一步是要处理对应的 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 将负责处理实际的创建逻辑。
有了这段代码,掌柜的可以添加新的菜品了:
POST /menus
POST /menus

3.3 @Get()

  • @Get():这个装饰器标识此方法处理 GET 请求。通常用于获取资源的列表。
  • findAll():直接调用 MenusServicefindAll 方法来获取所有菜单。
有了这段代码,客人们可以查看所有的菜品了:
GET /menus
GET /menus

3.4 @Get(':id')

  • @Get(':id'):这个装饰器标识此方法处理 GET 请求,并且路径中带有一个 id 参数。:id 是一个路径参数,表示特定资源的标识符。
  • findOne(@Param('id') id: string):这个方法使用 @Param('id') 从请求路径中提取 id 参数,并将其转换为字符串类型传递给 findOne 方法。
  • this.menusService.findOne(+id):调用 MenusServicefindOne 方法,传入的 id 参数前加上 + 表示将字符串 id 转换为数字。
有了这段代码,客人们可以查看某一道菜品的详情了:
GET /menus/1
GET /menus/1

3.5 @Patch(':id')

  • @Patch(':id'):这个装饰器标识此方法处理 PATCH 请求。PATCH 请求通常用于更新部分资源。
  • update(@Param('id') id: string, @Body() updateMenuDto: UpdateMenuDto):该方法从请求路径中提取 id 参数,从请求体中提取更新数据 updateMenuDto
  • this.menusService.update(+id, updateMenuDto):调用 MenusServiceupdate 方法,更新指定 id 的菜单。
有了这段代码,掌柜的可以修改菜品信息了:
PATCH /menus
PATCH /menus

3.6 @Delete(':id')

  • @Delete(':id'):这个装饰器标识此方法处理 DELETE 请求,通常用于删除资源。
  • remove(@Param('id') id: string):从请求路径中提取 id 参数。
  • this.menusService.remove(+id):调用 MenusServiceremove 方法,删除指定 id 的菜单。
有了这段代码,掌柜的可以删除菜品信息了:
DELETE /menus/1
DELETE /menus/1
至此,可以说前言部分的 API 接口都已经完成了。

3.7 @Query()

通过 3.3 的代码就可以查看所有的菜品,但是有时候希望通过名称 name 和类型 category 去过滤菜单,这就要用到查询字符串的装饰器 @Query ,假设用户可以通过以下接口拿到过滤菜单:
  • GET /menus/search?name=beef&category=Chinese
@Get('search') 装饰器将会匹配以 /menus/search 开头的路由。
⚠️ 注意:放在动态路由前面,防止被优先捕获。
对应的 menus.service.ts 中的 search 方法修改如下:
这样就可以分别从查询字符串中拿到 namecategory 的值,并分别赋值给对应参数,再调用 MenusServicesearch 方法,找到指定 namecategory 范围下的数据了。
现在,客人可以筛选菜品了:
GET /menus/search?name=beef&category=Chinese
GET /menus/search?name=beef&category=Chinese
GET /menus/search
GET /menus/search

4. 总结

这篇博客介绍了如何快速生成一个资源,以及控制器相关的内容。结合装饰器,控制器类能够负责处理与菜单相关的 HTTP 请求。它利用 NestJS 提供的装饰器来标识每个方法处理的请求类型(GET、POST、PATCH、DELETE)以及路径参数。其他的各种装饰器将会在以后用到时才会出现。
业务逻辑部分通过依赖注入的方式委托给 MenusService 服务类来处理。这样使得控制器更加简洁,专注于路由处理和请求解析,而将具体的业务逻辑交由服务类负责。下一篇将讲解服务类的相关内容。
 
Webpack5 系列(一):基础篇NestJS 🧑‍🍳 厨子必修课(二):项目创建
Eric 见嘉
Eric 见嘉
Less is more.
公告
type
status
date
slug
summary
tags
category
icon
password
💭
合抱之木,生于毫末;九层之台,起于累土;千里之行,始于足下。

关于我
土木转行的前端开发工程师,陆续分享技术干货。
联系我
微信公众号:见嘉 Being Dev