NestJS 项目架构设计:与 Express 的对比及设计哲学解析
NestJS 的核心设计哲学是提供一个开箱即用的应用架构,使开发者与团队能够创建高度可测试、可扩展、松耦合且易于维护的应用,该架构深受 Angular 启发。
背景:从 Express 项目手动搭建说起
在理解 NestJS 的设计优势之前,首先需要了解使用原生 Express 搭建一个具备基本分层架构的项目需要哪些手动工作。视频中准备了一个预先构建好的 Express 项目,该项目在中层进行了代码划分,分为路由定义、控制器(Controller)、服务(Service) 三个层次,其架构流程如下:
- main.js 为项目入口,从中引入相应的路由定义。
- 路由定义中定义一个路由对象,绑定根路径,将路径与 GET 请求绑定成一个端点,交由对应的 Controller(如
getHello)处理。 - Controller 中调用对应的 Service(如
appService),由 Service 返回字符串。
该项目实现热重载(Hot Reload)功能的运行命令为:
node --watch 相应文件其中 --watch 是 Node.js 内置的监听文件变更并自动重启的功能,需要注意的是:该功能仅在 Node.js V20 及以上版本 才可以使用。
关键观察:在 Express 项目中,无论是划分不同的文件夹结构,还是添加运行命令,所有这些工作全部需要手动完成——NestJS 并不会自动帮你生成这些配置。这些手动完成的工作,在 NestJS 项目中全部是自动处理好的。
NestJS 的设计哲学
NestJS 官网右侧的 "Philosophy"(设计哲学)部分展示了其核心理念:
Nest provides an out-of-box application architecture, which allows developers and teams to create highly testable, scalable, loosely and easily maintainable applications.
翻译:Nest 提供一个开箱即用的应用架构,允许开发者和团队创建高度可测试、可扩展、松耦合且易于维护的应用。
其中最重要的一句话为:
The architecture is heavily inspired by Angular.
即:该架构深受 Angular 的启发。因此,如果之前学习过 Angular,会发现 NestJS 中的许多设计方式和写法与 Angular 非常相似。
NestJS 项目创建:命令行工具与参数详解
创建命令与跳过步骤
创建 NestJS 项目的标准命令为:
nest new在创建过程中,NestJS 默认会自动安装项目依赖,并自动将项目初始化为 Git 仓库。如果希望跳过这两个步骤,需要通过命令行参数进行调整。官方文档中的 CLI 使用说明(位于官方文档左侧 "CLI" 下的 "Usage" 部分)提供了以下参数:
| 参数 | 完整写法 | 作用 |
|---|---|---|
| 跳过 Git 初始化 | --skip-git 或 -g | 不初始化 Git 仓库 |
| 跳过包管理器安装 | --skip-install 或 -s | 不自动安装依赖 |
| 选择编程语言 | -l(如 -l javascript) | 指定使用 JavaScript 而非默认的 TypeScript |
例如,快速创建一个名为 nest-app 的项目并跳过依赖安装和 Git 初始化的命令为:
nest new nest-app -g -s对比说明:如果不加 -g 和 -s,创建过程会等待较长时间,因为需要安装项目依赖;加上这两个参数后,项目创建会非常快,因为跳过了安装过程。
JavaScript 支持说明
NestJS 是可以通过 JavaScript 来创建的,但默认情况下创建的都是 TypeScript 项目。需要通过在选项中加 -l 参数选择对应的编程语言,否则默认以 TypeScript 为主。
架构可视化:使用 madge 工具生成依赖架构图
为了直观对比 Express 与 NestJS 的项目架构,需要使用一个名为 madge 的工具。该工具可以全局安装或安装在项目依赖中,用于扫描项目入口文件并生成依赖关系架构图。
使用步骤
- 全局安装 madge:
npm install -g madge- 以 Express 项目为例,生成架构图的命令为:
madge image 入口文件路径对于 Express 项目,入口文件是 main.js。
- 对于 NestJS 项目,入口文件是
src目录下的main.ts,命令为:
madge image src/main.ts输出的文件格式可以生成 PNG,也可以生成 SVG(视频中个人偏好 SVG 格式)。
架构图对比结果
生成后的架构图显示,NestJS 的架构与 Express 项目大差不差,但 NestJS 中多了一个叫做 Module(模块)的概念。
Module(模块):NestJS 架构的核心概念
什么是 Module
在 NestJS 的架构图中,Module 是将 Controller 和 Service 整合在一起的工具。可以把它当做一个简单的基座,这个基座上面可以承载不同的东西(Controller、Service 等),最后需要把这个承载了不同东西的基座本身作为创建项目的依赖。
因此,在 main.ts 中,NestJS 项目会导入 Module 文件,以它为基础来创建服务器对象。
Module 的官方定义与特性
官方文档 "Overview" 下的 "Modules" 部分对 Module 的说明包括以下要点:
- 每个 NestJS 项目至少都有一个 Module,即 Root Module(根模块)。根模块是整个 NestJS 项目构建的开始点(Starting Point),项目本身一定要基于一个 Module 来进行创建。
- 官方推荐以划分不同 Module 的形式来组织组件。官方原文表述为:
Modules are highly recommended as an effective way to organize your components.
即:强烈推荐将 Module 作为组织组件的有效方式。NestJS 项目的功能划分一般都会划分成不同的 Module 或模块。
Module 的定义方式
Module 的定义规则非常简单:一旦一个类带上了 `@Module` 装饰器,那么它本身就是一个 Module(模块)。因此,在 app.module.ts 文件上面都有 @Module 装饰器。
Module 文件内部结构
Module 作为一个基座,会引入 Controller 和 Service。在 @Module 装饰器中,会定义:
- controllers:该模块下承载的控制器
- providers:该模块下承载的服务(Service)
注意:为什么 Service 前面的属性名是
providers而不是services?这涉及 NestJS 中的依赖注入(Dependency Injection)和控制反转(Inversion of Control) 概念。这两个概念较为复杂,其中很多细节是隐藏起来的,需要单独深入讲解。
依赖注入与控制反转:待讲解的复杂概念
视频中明确指出,providers 与 controllers 的属性名差异,其本质原因在于依赖注入和控制反转。
- 要解决的问题:为什么 Controller 中能够直接引用
appService这个 Service,而不需要手动创建该 Service 的对象实例? - 复杂程度:这两个概念本身是比较复杂的,其很多细节被框架隐藏起来。
- 讲解安排:留到下一次再讲,本节的重点是理解 NestJS 的整个项目结构和设计哲学,而非深入依赖注入机制的内部原理。
NestJS 对比 Express 的核心优势总结
| 对比维度 | Express(手动搭建) | NestJS(开箱即用) |
|---|---|---|
| 架构划分 | 需要手动创建路由、Controller、Service 文件夹及文件 | 自动提供完整的应用架构 |
| 启动命令 | 需要手动编写 node --watch 等运行命令 | 启动命令全部写好在项目中 |
| Module 机制 | 无 | 每个项目至少有一个 Module,推荐按模块划分功能 |
| 依赖注入 | 需要手动创建对象实例 | 框架自动管理依赖,通过 providers 声明 |
| 设计来源 | — | 深受 Angular 启发 |
Module 概念的适应性问题与后续展望
对不同背景开发者的影响
- 学过 Angular 的开发者:会觉得 Module 的概念非常亲切,因为 NestJS 的 Module 划分方式和 Angular 中的 Module 划分方式一模一样。
- 只写过 React、Vue 或普通 Express 项目的开发者:可能会比较难以理解为什么要以 Module 的方式来划分,以及这样划分的好处到底是什么。
当前阶段的理解要求
在当前学习阶段,对于 Module 概念,只需要有一个简单的概念即可。Module 的实际好处会在后续做实际项目时得以体现。
限制与待确认问题
- 依赖注入与控制反转的具体机制和实现原理尚未在本节展开,等待后续课程讲解,且该部分内容较为复杂、细节隐藏较深。
- 视频中提到 madge 工具时,其名称发音为 "match"(原文语音表述),具体拼写为
madge。 - 视频中提到 "Nest 它是可以通过 JavaScript 来创建的",但之前并没有演示过,原因在于需要通过
-l参数选择编程语言,默认以 TypeScript 为主。 - 视频中的 "match" 工具需要先安装(全局或项目依赖)才能使用,本视频演示了全局安装方式。
- 视频提到 "Nest 可以通过 JavaScript 创建""我们之前好像并没有演示过"——本次视频同样未实际演示 JavaScript 创建过程,仅说明了命令参数。
- 原文视频为口语化教学内容,部分表述(如 "NeJ""list 项目""match")存在口音或发音导致的拼写不精确,本笔记依据上下文进行了合理还原(分别对应 NestJS、NestJS、madge)。
行动清单(学习者可实际操作验证)
- 使用
nest new project-name -g -s快速创建一个不包含 Git 初始化和依赖安装的 NestJS 项目。 - 使用
nest new project-name -l javascript创建 JavaScript 版本的 NestJS 项目(验证可用性,本视频未实际演示)。 - 全局安装 madge(
npm install -g madge),对 Express 项目(入口main.js)和 NestJS 项目(入口src/main.ts)分别执行madge image命令,对比生成的架构图。 - 对比观察:NestJS 架构图中多出的 Module 节点,与 Express 架构图的差异所在。
- 观察
app.module.ts中@Module装饰器内的controllers和providers属性,并尝试理解providers而非services命名的深层原因(留待后续课程解决)。
总结
本节的核心结论可以归纳为以下三点:
- NestJS 最大的能力是提供一个完整的、开箱即用的应用架构——不需要像 Express 项目那样手动创建文件夹结构、配置启动命令。
- 该架构深受 Angular 启发,因此了解 Angular 的开发者会感到设计模式上的熟悉感。
- Module 是 NestJS 架构中的核心新增概念——它作为基座将 Controller 和 Service 整合在一起,每个 NestJS 项目至少有一个根 Module,且官方推荐按模块划分功能;
providers的命名涉及依赖注入与控制反转机制,将在后续深入讲解。