Nest.js学习系列之一——初识Nest.js

983 阅读9分钟

这篇文章来自我们团队穆召同学的分享,如果说Angular是个“好”框架,但是学习它的“性价比”不够高的话,那Nest.js就是个“性价比”爆棚的Angular,一起来了解下吧~

NestJS 在 Node.js 后端框架中变得越来越流行,截至目前githubstar数目已经有56k,仅仅从star数来看,是仅次于express的存在。接下来我带大家一起认识一下 NestJS(文章后续统称为Nest)。

简介

1_1683200587475.png

把 Nest 官网的话翻译过来就是:一个渐进的 Node.js 框架,用于构建高效、可靠和可扩展的服务器端应用程序。说白了 Nest 就是一个 Node.js 服务端框架。

熟悉 Angular 的同学想必看 Nest 也会有熟悉的味道,都使用了依赖注入的设计模式。另外也是深受 Java 的 Spring 框架启发,也有不少人说Nest就是Node版本的Spring

除此之外,Nest 还有以下特点

  1. 完全使用Typescript开发,增强了项目的可靠性。
  2. Nest 提供了一个开箱即用的应用程序体系结构,主打一个拿来就能直接用,并且是有自己的一套架构模式的,不像 ExpressKoa 等轻量框架完整的搭建一个项目还需要DIY不少东西
  3. Nest 在 Express/Fastify 上提高了一个抽象级别,但仍然向开发者直接暴露了底层框架的 API,意思就是就是Nest提供了更规范的抽象层,但底层还是 Express/Fastify 框架(默认是Express),你也可以直接使用 Express 原生 API 去开发。

起步

Nest 提供了 Nest Cli,可以很方便的创建一个Nest项目, 执行以下命令来安装Nest Cli

npm i -g @nestjs/cli

安装好 Nest Cli 后,使用 nest 命令初始化一个项目

nest new my-project

初始化时会提示选择使用的包管理器:npm/yarn/pnpm,根据自己喜好选择,这里我们使用的是 pnpm,也推荐使用 pnpm,有兴趣的同学可以了解一下 pnpm 相对于 npm/yarn 的优势

2_1684483839888.png

命令运行完成,我们的Nest项目就创建完成了,包括项目依赖,Nest 也帮我们安装完成,我们只需要进入my-project 目录运行 pnpm start 就可以运行项目

3_1684484340994.png

此时打开浏览器,地址里输入http://localhost:3000, 就可以看到 Hello World!的响应了

4_1684486472672.png

这时候一个Nest项目就创建并运行成功了

项目结构

运行完项目后,我们肯行想要知道,项目是怎么运行的,下面就看一下Nest项目的结构,如下

5_1684488279844.png

Nest 的项目核心文件都在根目录的 src 文件夹内,结构如下

src
├── app.controller.spec.ts
├── app.controller.ts
├── app.module.ts
├── app.service.ts
└── main.ts

这里截取官网对这几个文件的简单介绍

6_1684489748407.png

入口文件

Nest项目的入口文件为main.ts, 内容如下:

7_1684489869535.png

代码内容很简单,使用@nest/core中的NestFactory.create方法直接创建应用,这里需要注意的是我们引入了AppModule作为参数,这个AppModule我们等会再说,然后调用app.listen(3000)去监听3000端口,就启动了一个 Node 服务。

模块

上面入口文件,我们引入了AppModule作为创建应用的参数,对应的文件为 src/app.module.ts, 该文件在Nest中被称为模块文件,如下

8_1684823131020.png

模块即为 @Module()装饰器修饰的类,这里的装饰器是 Typescript 中的概念,还不熟悉的同学可以自行去了解一下。每个 Nest 应用程序至少有一个模块,即根模块,我们目前初始化的项目中只有一个模块即根模块,是因为我们目前项目仅仅包含一个示例接口,实际上一般项目都会包含多个模块,每个模块都有一组紧密相关的功能,并且与其他模块有业务隔离。 @module() 装饰器接受一个对象作为参数,对象包含四个属性:

providers由 Nest 注入器实例化的提供者(服务),可以至少在整个模块中共享
controllers必须创建的一组控制器, 处理http请求
imports导入模块的列表,如需使用其他模块的服务,在这里导出模块
exports其他模块要使用本模块的服务,则需要本模块导出这些模块

控制器

控制器负责处理传入的请求和向客户端返回响应, 回顾前面的模块文件,引入了 app.controller.ts 文件,并注册到控制器列表中, app.controller.ts 即控制器文件,内容如下:

9_1684755933016.png

  • @Controller() 装饰AppController类用来定义一个控制器
  • @Get() 装饰getHello方法用来定义一个请求方法为GET,路径为/的路由,并绑定路由处理函数为getHello
  • AppControler 类的私有属性 appService 是通过依赖注入直接注入了 AppService 类的实例,不需要我们自己实例化。AppService 为什么能被直接注入使用,我们等会再说

此时,一个简单的控制器就声明完成,并且声明了一个路由 GET /, 这就是之前我们启动项目,访问http://localhost:3000 的路由。
实际上@Controller()@Get() 装饰器也是都可以接受参数的,比如我们将app.controller.ts文件修改为以下内容:

10_1684830539048.png

  • @Controller('home') 设置了该控制器下所有路由前缀为home
  • @Get('hello') 设置了单个路由路径为hello

此时我们项目声明的唯一一个路由变成了GET /home/hello, 重启项目,访问 http://localhost:3000 会返回404 

11_1684830838462.png

而访问 http://localhost:3000/home/hello 才会返回 Hello World!

12_1684830968489.png

同样 Nest 为所有标准的 HTTP 方法提供了相应的装饰器:@Post@Put()@Delete()等,同时提供了@Body()@Query()@Param()等装饰器获取请求参数,并且路由是支持通配符和参数的,这些内容后续文章再详细介绍。

提供者

回顾模块和控制器中,模块和控制器都引入了 src/app.service.ts 文件,app.service.ts 就是一个提供者文件,内容如下:

13_1684832516501.png

实际上提供者就是一个被@Injectable()装饰器装饰的类,因为此处提供者是用来处理业务逻辑,所以可以称为服务,回看模块和控制器文件中,模块将AppService注册到了providers列表中,控制器使用AppService作为私有属性appService类型,Nest就会实例化AppService 并将实例化的值赋值给appService,从而实现了依赖注入。这时候控制器就可以直接调用服务的方法来处理业务逻辑了。示例项目开始访问 http://localhost:3000 返回的 Hello World 就是服务中的getHello方法返回的结果。

我们来回顾一下Nest启动服务并实现接口全流程:控制器controller定义路由并处理请求和返回,服务provider来处理业务逻辑,将控制器和服务都注册到模块module中,然后通过 mian.js 启动服务并传入模块module作为参数,这样整个链路就实现完成了,还是非常简单的。

数据库

通过之前对模块,控制器,提供者的介绍,我们已经可以实现一个简单的路由,接下来我们一起来在 Nest中链接数据库,并实现数据的增删改查。

Nest 与数据库无关,允许您轻松地与任何 SQL 或 NoSQL 数据库集成

实际上Nest 能非常方便的集成各种关系型和非关系型数据库,这里因为我本机上有正在运行的mongodb容器,所以我们就选择非关系型数据库mongodb作为我们的示例数据库。mongodb是非常流行的非关系型数据库,实际上mongodb采用了与json非常相似bson数据结构来存储数据,并且查询语法与js比较相似,所以也是非常适合我们javascript开发者日常使用的,有兴趣的同学也可以去了解一下。因为mongodb是文档型数据库,所以,有几个概念我们需要先了解一下:

  • 文档(document):mongodb中的数据是以文档的形式存储的,一条数据就是一个文档,我们可以类比关系型数据库中的行
  • 集合(collection): 集合中包含多条文档,集合与文档的关系可以类比为关系型数据库中的表和行的关系

了解完基本概念,我们开始使用数据库

连接数据库

Nest支持两种与 MongoDB 数据库集成的方式, 一是使用内置的TypeORM, 二是使用mongodb最流行的包mongoose, 这里我们选择使用mongoose
首先需要安装以下依赖

$ pnpm install --save @nestjs/mongoose mongoose

安装完成后,我们可以将其 MongooseModule 导入到根模块 AppModule 即可。还是以开始初始化的项目作为示例,打开src/app.module.ts文件,添加两行代码,如下:

14_1684923728498.png

此时数据库连接就完成了,代码中MongooseModule.forRoot() 和 mongoose 包中的 mongoose.connect() 的参数是一样的,更详细的连接参数,可参考文档

模型注入

模型即schema, 类似于Sequelize中的modelTypeORM中的entity,不同的是modelentity是用来定义表结构的,而schema对应的是mongodb中的集合,用来定义集合中文档的结构的。
我们创建文件src/blog.schema.ts来定义博客的shcema, 内容如下:

15_1684927134518.png 上面schema中我们定义了一个blog的模型,并且对其字段进行了约束。有兴趣的同学可以了解一下schema完整的定义,可参考文档
然后在src/app.module.ts中导入BlogSchema:

16_1684927248778.png

此时,schema注入就完成了。

接口实现

编辑src/app.controller.ts, 如下:

17_1684927396787.png

我们定义了两个接口分别为:

  • 创建blog: POST /home/blog
  • 获取blog列表:GET /home/blogs

其中在 createBlog()方法中使用了@Body装饰器来装饰createBlogDto,这点之前我们是没有说的,@Body()装饰器用来获取请求body体内容并赋值给它所装饰的参数createBlogDto

控制器接受请求后将业务逻辑都委托给了appService服务去处理,接下来编辑src/app.service.ts,如下:

18_1684927737051.png

在构造函数中使用 @InjectModel('blog') 装饰 blogModelblog模型注入到了AppService中, 此时就可以直接调用this.blogModel.create(), this.blogModel.find() 来对数据库blog集合进行创建和查找操作了。

接下来我们运行pnpm start启动服务器, 然后打开postman请求创建接口:

19_1684928118426.png

可见数据已经创建成功,同样在我们的mongodb中也已经可以看到创建成功的数据了:

20_1684928234942.png

接着我们再来请求查询接口:

21_1684928288838.png

接口也是正常返回了创建的数据。此时数据库mongodb的连接和对数据库集合的创建,查询操作已经全部跑通了。

后续

本片文章是Nest的入门介绍,因为篇幅的原因,文章中很多知识点也是一笔带过,没有详细介绍,通过本文大家能对Nest有个初步认识就可以了,后续会继续出文章对Nest的各个模块做更详细深入的介绍。