Nest快速上手

88 阅读10分钟

快速创建

在学习 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。可以使用 进行访问。

http://localhost: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 请求。方法内部,我们通过调用 AppServicegetHello 方法来获取一个问候语字符串,并将这个字符串返回给客户端。通过这种方式,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 命令:

名称别名描述
newn搭建新的标准模式应用程序,其中包含运行所需的所有样板文件
generateg基于原理图生成和/或修改文件
build将应用程序或工作区编译为输出文件夹
start编译并运行应用程序(或工作区中的默认项目)
add编译并运行应用程序(或工作区中的默认项目)
infoi显示有关已安装的嵌套包的信息和其他有用的系统信息

generate 后可以加的选项如下:

名称别名描述
applicationapplication在单存储库中生成一个新应用程序(如果是标准结构,则转换为单存储库)
classcl生成一个新类
configurationconfigGenerate a CLI configuration file
controllerco生成控制器声明
decoratord生成自定义修饰器
filterf生成过滤器声明
gatewayga生成网关声明
guardgu生成保护声明
interceptoritc生成侦听器声明
interfaceitf生成接口
middlewaremi生成中间件声明
modulemo生成模块声明
pipepi生成管道声明
providerpr生成提供程序声明
resolverr生成解析程序声明
services生成服务声明
librarylib在单存储库中生成一个新库(如果是标准结构,则转换为单存储库)
sub-appappGenerate a new application within a monorepo
resourceres生成新的 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=1http://localhost:3000/user/1
post增改http://localhost:3000/user/addhttp://localhost:3000/user
delete删除http://localhost:3000/user/deletehttp://localhost:3000/user
put更新http://localhost:3000/user/updatehttp://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 Unauthorizedtoken错误
403 Forbidden referer origin验证失败
404 Not Found接口不存在
500 Internal Server Error服务端错误
502 Bad Gateway上游接口有问题或者服务器问题

持续更新...