学习Nest.js可以通过官方文档:Documentation | NestJS - A progressive Node.js framework。Nest官方文档如图2-1所示。

图2-1 Nest.js官方文档
英文掌握薄弱可采用全文翻译为中文或者在浏览器扩展中下载插件"沉浸式翻译 - 网页翻译插件 | PDF翻译 | 免费",配置后,可通过快捷键进行中英文对照翻译,更适合阅读理解,如图2-2所示。

图2-2 Nest.js官方文档-局部翻译
如果没有学习过Nest.js,第一个疑惑是它是干什么的?Nest.js是提供HTTP服务的框架。并且Nest.js是基于Express实现(即Nest.js底层是默认基于Express封装,也可以选择Fastify),并没有采用原生实现。
Express和Fastify要如何抉择?Express适合做业务层,而Fastify适合做网关层,根据自己实际情况决定。
2.1 安装Nest.js
假如你从未使用过Nest.js,那么第一步是先通过官方文档安装NestJS 命令行工具(CLI),安装后可以在系统的任何目录下使用nest 命令。通过win+R组合快捷键呼出运行窗口,输入cmd进入终端,复制以下命令回车执行。
Vue.js 2的主流时期也是采用CLI的方式构建项目,后续逐渐迁移到Vite中,而NestJS的CLI使用方式和过往的CLI都是一致的。
ts
//全局安装 NestJS 命令行工具(CLI)
npm i -g @nestjs/cli
安装结束之后,通过nest --version命令检测NestJS 命令行工具是否安装成功,如图2-3所示。

图2-3 Nest.js命令行工具安装
第二步就可以构建一个Nest.js项目,通过在终端输入以下命令构建项目。
ts
// nest new <项目名>
nest new app
会生成如下问题:Which package manager would you ❤️ to use?(您将使用哪个包管理器),有npm、yarn和pnpm可选,推荐pnpm。项目构建成功如图2-4所示。

图2-4 Nest.js构建项目
Nest.js构建的项目结构目录如图2-5所示。

图2-5 Nest.js项目结构目录
从上往下解释分为四部分:
(1)node_modules文件夹:各种项目依赖包,在构建项目时就装好了,无需再次安装。
(2)src文件夹:源代码存放处。
(3)test文件夹:端到端(E2E)测试目录,这个不管。
(4)各类配置文件。
src文件夹下有四个ts文件,作用如下:
ts
├── main.ts // 应用入口文件
├── app.module.ts // 根模块(AppModule)
├── app.controller.ts // 根控制器
├── app.service.ts // 根服务
└── app.controller.spec.ts // 控制器单元测试
配置文件总结如表1-1所示。
表1-1 Nest.js项目结构-配置文件
| 文件名 | 类型 | 主要作用 | 关联工具/框架 |
|---|---|---|---|
| package.json | 项目配置 | 定义项目依赖、脚本命令、基本信息 | Node.js / npm |
| package-lock.json | 依赖锁 | 锁定依赖版本,确保一致性 | npm |
| .gitignore | 忽略规则 | 指定哪些文件/目录不应提交到 Git | Git |
| .prettierrc | 代码格式化 | 配置代码格式化规则(Prettier) | Prettier |
| eslint.config.mjs | 代码检查 | 配置 ESLint 代码质量检查规则 | ESLint |
| nest-cli.json | CLI 配置 | NestJS 命令行工具的配置文件 | NestJS CLI |
| tsconfig.json | TS 配置 | TypeScript 编译器配置(开发环境) | TypeScript |
| tsconfig.build.json | TS 构建配置 | TypeScript 生产构建配置 | TypeScript / NestJS |
| jest-e2e.json | 测试配置 | 端到端(E2E)测试的 Jest 配置 | Jest |
| README.md | 文档 | 项目说明文档 | - |
以上是初始化项目的基础介绍。
2.2 src文件夹内容介绍
src文件夹下的app.controller.spec.ts单元测试文件在开发中是基本不会用到的,因此删除该文件。每次打包构建的时候都会重新创建该单元测试文件,因此我们在nest-cli.json配置文件中添加generateOptions配置项,将spec置为false,做完以上操作,构建项目就不会重新创建app.controller.spec.ts文件了。
ts
{
"$schema": "https://json.schemastore.org/nest-cli",
"collection": "@nestjs/schematics",
"sourceRoot": "src",
"compilerOptions": {
"deleteOutDir": true
},
"generateOptions": {
"spec": false
}
}
此时src文件夹下4个文件的关联性就很强,文件功能如下4点:
(1)app.controller.ts:控制层,类似前端的路由。
(2)app.module.ts:协调app.controller.ts和app.service.ts文件,我们可以将app.module.ts文件理解为依赖注入的容器,通过@Module()装饰器实现模块整合。
ts
//app.module.ts
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
@Module({
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
(3)app.service.ts:具体业务实现。
以上3个文件夹分层和MVC架构具备一定的相似性,只不过没有view(视图层)。
(4) main.ts:应用启动入口,创建应用并实现应用的端口监听。
项目启动命令从package.json中的scripts配置项中查看,使用npm run start或者npm run start:dev都可以。启动项目之后到main.ts文件中查看具体端口(默认是3000端口),然后到浏览器中输入http://localhost:3000/进行访问。
Nest.js项目启动后展现的内容是来自app.service.ts文件的具体业务内容,如图2-6所示。

图2-6 Nest.js项目启动-初始化效果
2.3 Nest命令
在终端输入Nest g --help就可以将所有的nest命令弹出,其中有些nest命令可用于创建nest元素,即nest文件,该部分命令总结如表1-2所示。
表1-2 Nest.js命令行部分命令
| 名称 (Name) | 别名 (Alias) | 描述 (Description) |
|---|---|---|
| application | application | 生成一个新的应用工作空间 |
| class | cl | 生成一个新的类 |
| configuration | config | 生成 CLI 配置文件 |
| controller | co | 生成控制器声明 |
| decorator | d | 生成自定义装饰器 |
| filter | f | 生成过滤器声明 |
| gateway | ga | 生成网关声明(WebSocket) |
| guard | gu | 生成守卫声明 |
| interceptor | itc | 生成拦截器声明 |
| interface | itf | 生成接口 |
| library | lib | 在 monorepo 中生成新的库 |
| middleware | mi | 生成中间件声明 |
| module | mo | 生成模块声明 |
| pipe | pi | 生成管道声明 |
| provider | pr | 生成提供者声明 |
| resolver | r | 生成 GraphQL 解析器声明 |
| resource | res | 生成新的 CRUD 资源 |
| service | s | 生成服务声明 |
| sub-app | app | 在 monorepo 中生成新的子应用 |
如果我们想在src文件夹下创建一个xiaoyu模块,我们需要找到src文件夹,创建xiaoyu文件夹,在xiaoyu文件夹下创建xiaoyu.module.ts文件,并且在app.module.ts中将XiaoyuModule注册到模块中。注意:我们可以在@Module()装饰器的imports配置项中导入其他模块。
ts
// app.module.ts
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { XiaoyuModule } from './xiaoyu/xiaoyu.module';
@Module({
imports: [XiaoyuModule],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
但如果通过命令,只需要在终端通过nest g mo xiaoyu就能自动化的完成以上操作,包括自动在app.module.ts文件中注册模块。在终端中体现为新增xiaoyu.module.ts文件,改变app.module.ts文件,如图2-7所示。这可以减少我们一定的心智负担。

图2-7 nest g mo xiaoyu命令效果
除了注册模块,还可以创建service服务层,命令为:nest g s xiaoyu;创建controller控制层,命令为:nest g co xiaoyu。如图2-8所示。xiaoyu.controller.ts和xiaoyu.service.ts文件通过命令生成后,会自动关联到xiaoyu.module.ts文件中,无需开发者额外补充操作。

图2-8 nest g s/co xiaoyu命令效果
我们可以看到这些命令都在一定程度上体现对应的功能,例如刚才三点,因此通过理解关联就很容易记住。
(1)nest g mo <模块名称>=> module(模块)。
(2)nest g s <服务名称>=> service(服务)。
(3)nest g co <控制器名称>=> controller(控制)。
但常用的命令操作主要为:nest g res <模块名称>。表1-2的解释是生成增删改查的资源,听起来并不好理解,实际是生成一份完整结构的增删改查模块--Demo级别。
当我们在终端输入该命令:nest g res user,会弹出一段话What transport layer do you use?(你要使用哪种传输层协议?),然后给出如下5种选择:
(1)REST API。通过传统的 HTTP 路由方式提供接口,是最常用、最直观的 Web 接口风格。
(2)GraphQL(code first)。通过代码定义 GraphQL 架构,让类型和解析逻辑都由代码自动生成。(不好用)
(3)GraphQL(schema first)。先写 .graphql Schema,再根据 Schema 编写解析逻辑,更适合规范化协作。(不好用)
(4)Microservice(non-HTTP)。微服务,通过消息队列、TCP、gRPC 等方式实现服务间通信。
(5)WebSockets。用于创建实时、双向通信的长连接服务,如聊天室或实时推送。
我们选择第一种REST API就可以了。
此时会弹出第二个问题:Would you like to generate CRUD entry points?(你想生成增删改查的模板吗?),选Y(Yes),然后就会开始生成资源。
nest g res user命令效果如图2-9所示。

图2-9 nest g res user命令效果
可以看到该命令一下就创建出controller、module、service三层文件(自动化配置),这些文件内部都会自动初始化一份基础的代码。其文件作用与2.2小节所介绍的src文件夹下的三个同类型文件的作用是一致的。因此展现的内容在user.service.ts文件中,而路由在user.controller.ts文件中。我们打开user.controller.ts文件可以看到默认的get和post方法以及一些参数方法。
如果想要测试Patch,Delete等请求,需要在VSCode中下载rest client,然后在test测试文件夹下创建index.http文件用于发送各种不同的请求来测试API接口。
ts
// user.controller.ts
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) {} // 来自业务层的展示内容
@Post()
create(@Body() createUserDto: CreateUserDto) {
return this.userService.create(createUserDto);
}
@Get()
findAll() {
return this.userService.findAll(); // 触发默认get请求后,返回业务层中的findAll方法下的展示内容。
}
@Get(':id')
findOne(@Param('id') id: string) {
return this.userService.findOne(+id);
}
@Patch(':id')
update(@Param('id') id: string, @Body() updateUserDto: UpdateUserDto) {
return this.userService.update(+id, updateUserDto);
}
@Delete(':id')
remove(@Param('id') id: string) {
return this.userService.remove(+id);
}
}
因此,只需要在原有URL的后缀添加/user就能访问到user模块下的默认get请求,而get请求的是user.service.ts业务层的内容,/user路由访问效果如图2-10所示。到这里,我们就理解控制层和业务层之间的联系合作(同时在模块层导入注册),并且完成了一次基础的模块创建并展示使用。

图2-10 /user路由的访问效果
除此之外还有dto和entities两个文件夹,两个文件夹作用如下所示:
(1)dto文件夹:数据验证层。主要功能有:定义 API 请求、数据验证(使用 class-validator)、数据转换(使用 class-transformer)等等。内部有create-user.dto.ts和update-user.dto.ts文件,分别用来创建和更新用户的DTO。例如数据验证的做法如下代码所示。
ts
// create-user.dto.ts
import {IsNotEmpty,IsString} from 'class-validator'
export class CreateUserDto {
@IsNotEmpty() // 验证-不能为空
@IsString() // 验证-需要是一个字符串
account: string; // 账号
@IsNotEmpty()
@IsString()
password: string; // 密码
}
(2)entities文件夹:实体层。定义数据库的,即映射数据库表结构(ORM 概念),但我们数据库使用Prisma7,所以不在entities文件夹定义,这里用不上了。
所以nest g res <模块名称>做到了nest g mo/s/co <模块名称>的合并效果并且还有额外的数据验证层和实体层,因此nest g res相对于连续写3个命令来创建对应模块结构会更为简便,在日常使用会更加高频。