快速创建
在学习 NestJS 之前请先确保操作系统中安装过 Node.js(版本 >= 16)
本篇文章使用的Node.js版本是16.19.0。
使用 Nest 命令行接口 设置新项目非常简单。安装 npm 后,你可以在操作系统终端中使用以下命令创建一个新的 Nest 项目:
$ npm i -g @nestjs/cli
$ nest new nest-study
创建项目时,要求你选择包管理工具(npm,yarn,pnpm),本篇文章选择的是 npm 作为包管理工具。
然后使用npm run start
命令运行项目,项目的默认端口是3000。可以使用 进行访问。
还可以使用 npm run start:dev
命令运行项目,此命令将监视你的文件,自动重新编译并重新加载服务器。
基本概念
运行起来后,来分析项目目录核心结构,重点关注 src 文件夹里的内容。
文件名 | 描述 |
---|---|
app.controller.spec.ts | 具有单一路由的基本控制器 |
app.controller.ts | 控制器的单元测试 |
app.module.ts | 应用的根模块 |
app.service.ts | 具有单一方法的基本服务 |
main.ts | 使用核心函数 NestFactory 创建 Nest 应用实例的应用入口文件 |
首先从入口文件 main.ts 开始介绍。
// 引入 NestJS 的 NestFactory 类,用于创建 NestJS 应用程序的实例
import { NestFactory } from '@nestjs/core';
// 引入应用的主模块,它是 NestJS 应用程序的根模块
import { AppModule } from './app.module';
// 定义一个异步的 bootstrap 函数,用于启动 NestJS 应用程序
async function bootstrap() {
// 使用 NestFactory 的 create 方法创建 NestJS 应用程序的实例,传入应用的主模块 AppModule
const app = await NestFactory.create(AppModule);
// 调用 app 实例的 listen 方法,启动 HTTP 服务器并监听 3000 端口
// 注意:listen 方法也是异步的,因此使用 await 等待其完成
await app.listen(3000);
}
// 调用 bootstrap 函数启动 NestJS 应用程序
bootstrap();
要创建 Nest 应用实例,就需要使用到核心 NestFactory
类。使用它的create()
方法返回一个应用对象,通过调用这个实例的 listen()
方法,启动了一个 HTTP 服务器并让它监听在 3000 端口上。
接下来看 ./app.module.ts 文件中的内容。
// 引入 NestJS 的 Module 装饰器,用于定义 NestJS 的模块
import { Module } from '@nestjs/common';
// 引入当前应用中定义的 AppController 类,它负责处理进入的 HTTP 请求
import { AppController } from './app.controller';
// 引入当前应用中定义的 AppService 类,它封装了应用中的业务逻辑
import { AppService } from './app.service';
// 使用 @Module 装饰器来定义一个模块,这里是根模块 AppModule
@Module({
// imports 数组用于导入其他模块,以便在当前模块中使用它们的功能
// 在这个例子中,根模块没有导入其他模块,所以数组为空
imports: [],
// controllers 数组列出了当前模块中所有的控制器类
// 这里只列出了 AppController,因为它是处理 HTTP 请求的类
controllers: [AppController],
// providers 数组列出了当前模块中所有的服务提供者
// 这些服务提供者可以是任何值或类,但通常它们封装了应用的业务逻辑
// 在这个例子中,只列出了 AppService,它可能包含了一些与数据库交互、处理业务逻辑等功能的方法
providers: [AppService],
})
// 导出 AppModule 类,使其可以在其他模块中被导入
export class AppModule {}
在这个 AppModule
类中,我们使用 @Module
装饰器来定义了一个 NestJS 模块。这个模块是应用的根模块,它包含了应用所需的控制器(AppController
)和服务提供者(AppService
)。imports
数组用于导入其他模块,但在这个例子中它是空的,因为根模块不需要导入其他模块。controllers
数组列出了所有控制器类,这些类负责处理进入的 HTTP 请求。providers
数组列出了所有服务提供者,这些服务提供者可以是任何值或类,但在这个例子中,它只包含了一个服务类 AppService
,这个类可能封装了应用的业务逻辑。
然后来看 app.controller.ts 文件中的内容
// 引入 NestJS 的 Controller 和 Get 装饰器,用于定义路由处理器
import { Controller, Get } from '@nestjs/common';
// 引入 AppService 类,它可能封装了应用的业务逻辑,包括返回问候语的方法
import { AppService } from './app.service';
// 使用 @Controller 装饰器定义一个控制器类 AppController
// 由于没有指定任何路由路径,这个控制器将处理根路径 '/' 的请求
@Controller()
export class AppController {
// 在构造函数中,通过依赖注入的方式获取 AppService 的实例
// private readonly appService: AppService 表示这是一个只读属性,其值将在构造函数中初始化
constructor(private readonly appService: AppService) {}
// 使用 @Get 装饰器定义一个路由处理器方法 getHello
// 这个方法将处理 GET 请求,并且由于它没有被指定任何路由路径,它将处理根路径 '/' 的 GET 请求
@Get()
getHello(): string {
// 调用 AppService 的 getHello 方法获取问候语,并返回这个问候语
return this.appService.getHello();
}
}
在这个 AppController
类中,我们定义了一个路由处理器方法 getHello
,它使用 @Get()
装饰器来指定这个方法将处理对根路径 /
的 GET 请求。方法内部,我们通过调用 AppService
的 getHello
方法来获取一个问候语字符串,并将这个字符串返回给客户端。通过这种方式,NestJS 的控制器负责处理 HTTP 请求,并将业务逻辑委托给服务提供者(在这个例子中是 AppService
)。
然后来看 app.service.ts 文件中的内容。
// 引入 NestJS 的 Injectable 装饰器,用于标记一个类为可注入的服务提供者
import { Injectable } from '@nestjs/common';
// 使用 @Injectable 装饰器标记 AppService 类为可注入的服务提供者
@Injectable()
export class AppService {
// 定义一个名为 getHello 的方法,该方法没有接收任何参数
// 方法返回一个字符串 'Hello World!',这个字符串可以被视为一个简单的问候语
getHello(): string {
return 'Hello World!';
}
}
在这个 AppService
类中,我们使用 @Injectable()
装饰器来标记这个类是一个可注入的服务提供者。这意味着 NestJS 的依赖注入系统可以创建这个类的实例,并将其注入到需要它的地方,比如控制器中。getHello
方法是一个简单的示例,它返回了一个固定的字符串 'Hello World!'
,但在实际应用中,这个方法可能会包含更复杂的逻辑,比如从数据库中查询数据、调用其他服务或执行其他业务逻辑。
再来看 app.controller.spec.ts 文件里的内容。
// 引入 NestJS 测试模块的相关类和装饰器
import { Test, TestingModule } from '@nestjs/testing';
// 引入被测试的控制器类 AppController
import { AppController } from './app.controller';
// 引入被控制器依赖的服务类 AppService
import { AppService } from './app.service';
// 使用 describe 定义一个测试套件,用于测试 AppController
describe('AppController', () => {
// 声明一个变量 appController,用于存储 AppController 的实例
let appController: AppController;
// 使用 beforeEach 钩子函数在每个测试用例执行之前运行
// 它将创建一个测试模块,并编译它,然后从中获取 AppController 的实例
beforeEach(async () => {
// 使用 Test.createTestingModule 方法创建一个测试模块
// 传入一个对象,指定控制器数组(包含 AppController)和服务提供者数组(包含 AppService)
const app: TestingModule = await Test.createTestingModule({
controllers: [AppController], // 指定测试模块中要包含的控制器
providers: [AppService], // 指定测试模块中要包含的服务提供者
}).compile(); // 编译测试模块
// 使用 app.get 方法从测试模块中获取 AppController 的实例
// 并将其赋值给之前声明的 appController 变量
appController = app.get<AppController>(AppController);
});
// 使用 describe 定义一个子测试套件,用于测试根路由('/')的 GET 请求
describe('root', () => {
// 使用 it 定义一个测试用例,测试 getHello 方法是否返回 "Hello World!"
it('should return "Hello World!"', () => {
// 调用 appController 实例的 getHello 方法,并使用 expect 断言其返回值是否为 'Hello World!'
expect(appController.getHello()).toBe('Hello World!');
});
});
});
在这个文件中使用 Jest(NestJS 默认使用的测试框架)编写了单元测试示例,用于测试 AppController
类的 getHello
方法。它使用了 NestJS 提供的测试工具来创建一个测试模块,该模块包含了 AppController
和它依赖的 AppService
。然后,它编译这个测试模块,并从中获取 AppController
的实例,最后通过调用 getHello
方法并断言其返回值来验证 AppController
的行为是否符合预期。
Nest基本命令
NestJS 提供了一套丰富的命令来帮助开发者快速搭建和管理项目,这些命令主要通过 Nest CLI(Nest 命令行接口)来执行。
以下是一些常用的 NestJS 命令:
名称 | 别名 | 描述 |
---|---|---|
new | n | 搭建新的标准模式应用程序,其中包含运行所需的所有样板文件 |
generate | g | 基于原理图生成和/或修改文件 |
build | 将应用程序或工作区编译为输出文件夹 | |
start | 编译并运行应用程序(或工作区中的默认项目) | |
add | 编译并运行应用程序(或工作区中的默认项目) | |
info | i | 显示有关已安装的嵌套包的信息和其他有用的系统信息 |
generate 后可以加的选项如下:
名称 | 别名 | 描述 |
---|---|---|
application | application | 在单存储库中生成一个新应用程序(如果是标准结构,则转换为单存储库) |
class | cl | 生成一个新类 |
configuration | config | Generate a CLI configuration file |
controller | co | 生成控制器声明 |
decorator | d | 生成自定义修饰器 |
filter | f | 生成过滤器声明 |
gateway | ga | 生成网关声明 |
guard | gu | 生成保护声明 |
interceptor | itc | 生成侦听器声明 |
interface | itf | 生成接口 |
middleware | mi | 生成中间件声明 |
module | mo | 生成模块声明 |
pipe | pi | 生成管道声明 |
provider | pr | 生成提供程序声明 |
resolver | r | 生成解析程序声明 |
service | s | 生成服务声明 |
library | lib | 在单存储库中生成一个新库(如果是标准结构,则转换为单存储库) |
sub-app | app | Generate a new application within a monorepo |
resource | res | 生成新的 CRUD 资源 |
//创建user模块,也可以使用 mo 别名生成
nest g module user
nest g mo user
//创建user控制器,也可以使用 co 别名生成
nest g controller user
nest g co user
//创建user服务,也可以使用 s 别名生成
nest g service user
nest g s user
像上方形式执行三次命令未免有些麻烦,你可以执行下方命令也可以实现创建模块、控制器以及服务的效果
nest g resource user
不过需要注意的是,在执行时需要选择风格,本篇文章选择的是 RESTful 风格。
RESTful风格
什么是RESTful
RESTFUL是一种网络应用程序的设计风格和开发方式,基于HTTP,可以使用 XML 格式定义或 JSON 格式定义。
RESTful和传统风格对比
请求方式 | 含义 | 传统风格 | RESTful |
get | 查询所有 | http://localhost:3000/user/ | http://localhost:3000/user |
查询单个 | http://localhost:3000/user/list?id=1 | http://localhost:3000/user/1 | |
post | 增改 | http://localhost:3000/user/add | http://localhost:3000/user |
delete | 删除 | http://localhost:3000/user/delete | http://localhost:3000/user |
put | 更新 | http://localhost:3000/user/update | http://localhost:3000/user |
版本控制
如果想在Nest中使用,需要在./src/main.ts
中添加以下内容:
import { NestFactory } from '@nestjs/core';
import { VersioningType } from "@nestjs/common"
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.enableVersioning({
type: VersioningType.URI
})
await app.listen(3000);
}
bootstrap();
以user为例,在user.controller.ts
中,将@Controller('user')
修改为
@Controller({
path: "user",
version: "1"
})
此时我们再请求http://localhost:3000/user
就会显示404,需要在 user 前加上版本号,即http://localhost:3000/v1/user
。
像上方的做法,就将所有请求方式都设置了版本。那如果我们只想设置某一种请求方式呢?
当然也是可以的,如给 user 中的 get 请求加上版本。
@Get(':id')
@version('1')
findOne(@Param('id') id: string) {
return this.userService.findOne(+id);
}
code码规范
code码 | 说明 |
---|---|
200 OK | 成功 |
304 Not Modified | 协商缓存 |
400 Bad Request | 参数错误 |
401 Unauthorized | token错误 |
403 Forbidden referer origin | 验证失败 |
404 Not Found | 接口不存在 |
500 Internal Server Error | 服务端错误 |
502 Bad Gateway | 上游接口有问题或者服务器问题 |