Skip to content

NestJS介绍 ​

Nest (NestJS) 是一个用于构建高效、可扩展的 Node.js 服务器端应用程序的开发框架。它利用JavaScript 的渐进增强的能力,使用并完全支持 TypeScript (仍然允许开发者使用纯 JavaScript 进行开发),并结合了 OOP (面向对象编程)、FP (函数式编程)和 FRP (函数响应式编程)。 它提供了一组丰富的工具和模块来帮助开发人员构建高效、可扩展的服务器端应用程序。这些元素可以通过依赖注入系统自动注册,并可以在整个应用程序中共享。

NestJS优势 ​

  • 构建在现代 JavaScript 栈之上,因此使用了最新的 JavaScript 技术。
  • 基于 Angular 的架构和语法,提供了强大的模块化系统和依赖注入功能。
  • 基于 TypeScript,提供了强类型和静态类型检查。
  • 提供了丰富的工具和模块,可用于构建各种类型的服务器端应用程序,包括 RESTful API、GraphQL API、WebSocket 服务器等。
  • 提供了一组可扩展的构建块,可用于快速构建应用程序。
  • 提供了与主流数据库和身份验证系统的集成。

设计思想 ​

nestJS经常被调侃为springJS,所以这里参考java项目的阿里分层规范,其架构图如下:

终端显示层:各个端的模板渲染并执行显示的层。当前主要是velocity渲染,JS渲染,JSP渲染,移动端展示等。 开放接口层:可直接封装Service方法暴露成RPC接口;通过Web封装成http接口;进行网关安全控制、流量控制等。 Web层:主要是对访问控制进行转发,各类基本参数校验,或者不复用的业务简单处理等。 Service层:相对具体的业务逻辑服务层。 Manager层:通用业务处理层,它有如下特征:

  1. 对第三方平台封装的层,预处理返回结果及转化异常信息;
  2. 对Service层通用能力的下沉,如缓存方案、中间件通用处理;
  3. 与DAO层交互,对多个DAO的组合复用。 DAO层:数据访问层,与底层MySQL、Oracle、Hbase等进行数据交互。 外部接口或第三方平台:包括其它部门RPC开放接口,基础平台,其它公司的HTTP接口。

IOC ​

后端系统有很多的对象,这些对象之间的关系错综复杂,如果手动创建并组装对象比较麻烦,所以后端框架一般都提供了 IoC 机制。

IoC 机制是在 class 上标识哪些是可以被注入的,它的依赖是什么,然后从入口开始扫描这些对象和依赖,自动创建和组装对象。这种依赖注入的方式叫做 Dependency Injection,简称 DI。

Nest 里通过 @Controller 声明可以被注入的 controller ,通过 @Injectable 声明可以被注入也可以注入别的对象的 provider ,然后在 @Module 声明的模块里引入。

并且 Nest 还提供了 Module 和 Module 之间的 import ,可以引入别的模块的 provider 来注入。

ts
@Controller()  //这个class要放到IOC 容器里,然后让工具自动帮我们组装好
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Get()
  getHello(): string {
    return this.appService.getHello();
  }
  
  @Inject(DepartmentService)
  private departmentService: DepartmentService;

  @Inject(UserService)
  private userService: UserService;
}

项目中的依赖注入 ​

nest cli 会自动创建项目结构并安装依赖

sh
npx nest new nest-ioc
npm run start

构造器注入 ​

它有一个 AppService 声明了 @Injectable,代表这个 class 可注入,那么 nest 就会把它的对象放到 IOC 容器里。

ts
import { Injectable } from '@nestjs/common';

@Injectable()
export class AppService {
  getHello(): string {
    return 'Hello World!';
  }
}

AppController 声明了 @Controller,代表这个 class 可以被注入,nest 也会把它放到 IoC 容器里。

AppController 的构造器参数依赖了 AppService。

ts
import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';

@Controller()
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Get()
  getHello(): string {
    return this.appService.getHello();
  }
}

属性注入 ​

ts
  @Inject(DepartmentService)
  private departmentService: DepartmentService;

5种HTTP数据传输方式 ​

url param ​

我们可以把参数写在 url 中,比如:http://guang.zxg/person/1111,这里的 1111 就是路径中的参数(url param),服务端框架或者单页应用的路由都支持从 url 中取出参数。

NestJS实现 url param 是 url 中的参数,Nest 里通过 :参数名 的方式来声明(比如下面的 :id),然后通过 @Param(参数名) 的装饰器取出来注入到 controller:

ts
@Controller('api/person')
export class PersonController {
  @Get(':id')
  urlParam(@Param('id') id: string) {
    return `received: id=${id}`;
  }
}

@Controller('api/person') 的路由和 @Get(':id') 的路由会拼到一起,也就是只有 /api/person/xxx 的 get 请求才会走到这个方法。

query ​

常规传递

js
http://guang.zxg/person?name=guang&age=20

非英文的字符和一些特殊字符要经过编码,可以使用 encodeURIComponent 的 api 来编码:

js
const query = "?name=" + encodeURIComponent('光') + "&age=20"// ?name=%E5%85%89&age=20

使用封装了一层的 query-string 库来处理

js
const queryString = require('query-string');

queryString.stringify({
  name: '光',
  age: 20
});  // ?name=%E5%85%89&age=20

如果使用axios,axios 会做 url encode

NestJS实现 在 Nest 里,通过 @Query 装饰器来取:

ts
@Controller('api/person')
export class PersonController {
  @Get('find')
  query(@Query('name') name: string, @Query('age') age: number) {
    return `received: name=${name},age=${age}`;
  }
}

注意,这个 find 的路由要放到 :id 的路由前面,因为 Nest 是从上往下匹配的,如果放在后面,那就匹配到 :id 的路由了。

form-urlencoded ​

直接用 form 表单提交数据就是这种,它和 query 字符串的方式的区别只是放在了 body 里,前端代码使用 post 方式请求,指定 content type 为 application/x-www-form-urlencoded,用 qs 做下 url encode:

NestJS实现 用 Nest 接收的话,使用 @Body 装饰器,Nest 会解析请求体,然后注入到 dto 中。 dto 是 data transfer object,就是用于封装传输的数据的对象:

ts
export class CreatePersonDto {
    name: string;
    age: number;
}
ts
import { CreatePersonDto } from './dto/create-person.dto';

@Controller('api/person')
export class PersonController {
  @Post()
  body(@Body() createPersonDto: CreatePersonDto) {
    return `received: ${JSON.stringify(createPersonDto)}`
  }
}

form-data ​

form data 不再是通过 & 分隔数据,而是用 --------- + 一串数字做为 boundary 分隔符。因为不是 url 的方式了,自然也不用再做 url encode。 form-data 需要指定 content type 为 multipart/form-data,然后指定 boundary 也就是分割线。 很明显,这种方式适合传输文件,而且可以传输多个文件。 前端代码使用 axios 发送 post 请求,指定 content type 为 multipart/form-data

NestJS实现 Nest 解析 form data 使用 FilesInterceptor 的拦截器,用 @UseInterceptors 装饰器启用,然后通过 @UploadedFiles 来取。非文件的内容,同样是通过 @Body 来取。

ts
import { AnyFilesInterceptor } from '@nestjs/platform-express';
import { CreatePersonDto } from './dto/create-person.dto';

@Controller('api/person')
export class PersonController {
  @Post('file')
  @UseInterceptors(AnyFilesInterceptor({
      dest: 'uploads/'
  }))
  body2(@Body() createPersonDto: CreatePersonDto, @UploadedFiles() files: Array<Express.Multer.File>) {
    console.log(files);
    return `received: ${JSON.stringify(createPersonDto)}`
  }
}

json ​

form-urlencoded 需要对内容做 url encode,而 form-data 则需要加很长的 boundary,两种方式都有一些缺点。如果只是传输 json 数据的话,不需要用这两种。

前端代码使用 axios 发送 post 请求,默认传输 json 就会指定 content type 为 application/json,不需要手动指定

NestJS实现 同样使用 @Body 来接收,不需要做啥变动。form urlencoded 和 json 都是从 body 取值,Nest 内部会根据 content type 做区分,使用不同的解析方式。

ts
@Controller('api/person')
export class PersonController {
  @Post()
  body(@Body() createPersonDto: CreatePersonDto) {
    return `received: ${JSON.stringify(createPersonDto)}`
  }
}