这篇文章来自我们团队穆召同学的分享,如果说Angular是个“好”框架,但是学习它的“性价比”不够高的话,那Nest.js就是个“性价比”爆棚的Angular,一起来了解下吧~
NestJS
在 Node.js
后端框架中变得越来越流行,截至目前github
上star
数目已经有56k
,仅仅从star
数来看,是仅次于express
的存在。接下来我带大家一起认识一下 NestJS
(文章后续统称为Nest
)。
简介
把 Nest
官网的话翻译过来就是:一个渐进的 Node.js
框架,用于构建高效、可靠和可扩展的服务器端应用程序。说白了 Nest
就是一个 Node.js
服务端框架。
熟悉 Angular
的同学想必看 Nest
也会有熟悉的味道,都使用了依赖注入的设计模式。另外也是深受 Java
的 Spring
框架启发,也有不少人说Nest
就是Node
版本的Spring
。
除此之外,Nest
还有以下特点
- 完全使用
Typescript
开发,增强了项目的可靠性。 Nest
提供了一个开箱即用的应用程序体系结构,主打一个拿来就能直接用,并且是有自己的一套架构模式的,不像Express
,Koa
等轻量框架完整的搭建一个项目还需要DIY不少东西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
的优势
命令运行完成,我们的Nest
项目就创建完成了,包括项目依赖,Nest
也帮我们安装完成,我们只需要进入my-project
目录运行 pnpm start
就可以运行项目
此时打开浏览器,地址里输入http://localhost:3000
, 就可以看到 Hello World!
的响应了
这时候一个Nest
项目就创建并运行成功了
项目结构
运行完项目后,我们肯行想要知道,项目是怎么运行的,下面就看一下Nest
项目的结构,如下
Nest
的项目核心文件都在根目录的 src
文件夹内,结构如下
src
├── app.controller.spec.ts
├── app.controller.ts
├── app.module.ts
├── app.service.ts
└── main.ts
这里截取官网对这几个文件的简单介绍
入口文件
Nest
项目的入口文件为main.ts
, 内容如下:
代码内容很简单,使用@nest/core
中的NestFactory.create
方法直接创建应用,这里需要注意的是我们引入了AppModule
作为参数,这个AppModule
我们等会再说,然后调用app.listen(3000)
去监听3000
端口,就启动了一个 Node
服务。
模块
上面入口文件,我们引入了AppModule
作为创建应用的参数,对应的文件为 src/app.module.ts
, 该文件在Nest
中被称为模块文件,如下
模块即为 @Module()
装饰器修饰的类,这里的装饰器是 Typescript
中的概念,还不熟悉的同学可以自行去了解一下。每个 Nest
应用程序至少有一个模块,即根模块,我们目前初始化的项目中只有一个模块即根模块,是因为我们目前项目仅仅包含一个示例接口,实际上一般项目都会包含多个模块,每个模块都有一组紧密相关的功能,并且与其他模块有业务隔离。 @module()
装饰器接受一个对象作为参数,对象包含四个属性:
providers | 由 Nest 注入器实例化的提供者(服务),可以至少在整个模块中共享 |
---|---|
controllers | 必须创建的一组控制器, 处理http请求 |
imports | 导入模块的列表,如需使用其他模块的服务,在这里导出模块 |
exports | 其他模块要使用本模块的服务,则需要本模块导出这些模块 |
控制器
控制器负责处理传入的请求和向客户端返回响应, 回顾前面的模块文件,引入了 app.controller.ts
文件,并注册到控制器列表中, app.controller.ts
即控制器文件,内容如下:
@Controller()
装饰AppController
类用来定义一个控制器@Get()
装饰getHello
方法用来定义一个请求方法为GET
,路径为/
的路由,并绑定路由处理函数为getHello
AppControler
类的私有属性appService
是通过依赖注入直接注入了AppService
类的实例,不需要我们自己实例化。AppService
为什么能被直接注入使用,我们等会再说
此时,一个简单的控制器就声明完成,并且声明了一个路由 GET /
, 这就是之前我们启动项目,访问http://localhost:3000
的路由。
实际上@Controller()
, @Get()
装饰器也是都可以接受参数的,比如我们将app.controller.ts
文件修改为以下内容:
@Controller('home')
设置了该控制器下所有路由前缀为home
@Get('hello')
设置了单个路由路径为hello
此时我们项目声明的唯一一个路由变成了GET /home/hello
, 重启项目,访问 http://localhost:3000
会返回404
而访问 http://localhost:3000/home/hello
才会返回 Hello World!
同样 Nest
为所有标准的 HTTP
方法提供了相应的装饰器:@Post
,@Put()
,@Delete()
等,同时提供了@Body()
,@Query()
,@Param()
等装饰器获取请求参数,并且路由是支持通配符和参数的,这些内容后续文章再详细介绍。
提供者
回顾模块和控制器中,模块和控制器都引入了 src/app.service.ts
文件,app.service.ts
就是一个提供者文件,内容如下:
实际上提供者就是一个被@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
文件,添加两行代码,如下:
此时数据库连接就完成了,代码中MongooseModule.forRoot()
和 mongoose
包中的 mongoose.connect()
的参数是一样的,更详细的连接参数,可参考文档。
模型注入
模型即schema
, 类似于Sequelize
中的model
或TypeORM
中的entity
,不同的是model
或entity
是用来定义表结构的,而schema
对应的是mongodb
中的集合,用来定义集合中文档的结构的。
我们创建文件src/blog.schema.ts
来定义博客的shcema
, 内容如下:
上面
schema
中我们定义了一个blog
的模型,并且对其字段进行了约束。有兴趣的同学可以了解一下schema
完整的定义,可参考文档。
然后在src/app.module.ts
中导入BlogSchema
:
此时,schema
注入就完成了。
接口实现
编辑src/app.controller.ts
, 如下:
我们定义了两个接口分别为:
- 创建blog:
POST /home/blog
- 获取blog列表:
GET /home/blogs
其中在 createBlog()
方法中使用了@Body
装饰器来装饰createBlogDto
,这点之前我们是没有说的,@Body()
装饰器用来获取请求body
体内容并赋值给它所装饰的参数createBlogDto
。
控制器接受请求后将业务逻辑都委托给了appService
服务去处理,接下来编辑src/app.service.ts
,如下:
在构造函数中使用 @InjectModel('blog')
装饰 blogModel
将blog
模型注入到了AppService
中, 此时就可以直接调用this.blogModel.create(), this.blogModel.find()
来对数据库blog
集合进行创建和查找操作了。
接下来我们运行pnpm start
启动服务器, 然后打开postman
请求创建接口:
可见数据已经创建成功,同样在我们的mongodb
中也已经可以看到创建成功的数据了:
接着我们再来请求查询接口:
接口也是正常返回了创建的数据。此时数据库mongodb
的连接和对数据库集合的创建,查询操作已经全部跑通了。
后续
本片文章是Nest
的入门介绍,因为篇幅的原因,文章中很多知识点也是一笔带过,没有详细介绍,通过本文大家能对Nest
有个初步认识就可以了,后续会继续出文章对Nest
的各个模块做更详细深入的介绍。