第一章介绍了 NestJs 框架,初步实现了后端整个流程的套路(包括 controller,service 等等)数据库以及 ORM 的连接和使用
介绍
你好啊👋
这里是一个前端初学者学习 NestJs 的经历流程记录 ~
目标很简单:
- 使用 Nest 构建标准的 CRUD Restful API 来熟悉常见的全栈开发场景
- 生产一些所谓 pet project,需要简单的后端进行数据交互
我会尝试涵盖:
- 身份验证 authentication
- 单元测试 e2e tests
- 通过 ORM 连接常见的数据库 such as prisma
涉及到 JavaScript 和 TypeScript 的地方将不进行详解 ~
为什么 NestJs
不要把 NestJs 和 NextJs 搞混了,NextJs 是一个前端框架
NestJs 是一个 node.js backend framework,完全支持 TypeScript
和 express.js 不同,它解决了架构问题 -> 当项目快速增长时,如果开发经验不够丰富 express 就有可能变得一团糟
或许您也可以理解 NestJs 是更高级的 express 抽象
所以使用 NestJs 的理由:
- 模块化开发 modularity
- 架构 structure
- 语言支持 typescript
- API 集成 rest api & graphql
- 微服务 microservices
- 专注后端的文档和社区 doc & community
创建第一个项目
接下来我们就开始用 nestjs 启动一个 rest api 的后端
包含基础的 crud (create, read, update, delete) 功能
初始化
当然需要先安装 node.js 和 nestjs ~
使用 -g 参数,全局安装
npm i -g @nestjs/cli
上 nest.js 的 cli 工具
然后打开 cli,在合适的地方创建我们的 nestjs 项目
他会询问你使用哪种包管理,npm、yarn 或是 pnpm,我更喜欢使用 pnpm
js
nest new my-nestjs-api-tutorial
回顾
- 项目应该正常被创建了
- node 和 nest 正常运行
接下来导航到我们创建的后端项目,里面应该是这样的(版本不同应该是差不多的)
你可以用你喜欢的 IDE 打开,我更习惯 webstorm
,vscode 或 vim 都是相当好的选择

这是我们的关键源码 source code 所在地

一些端对端 e2e (end to end) 测试

整个项目的配置信息(后面会慢慢讲解,如果您是前端或许相当熟悉它们了)

上手修改一些代码
我们先删除这些可以忽略的文件,controller,controller.spec 和 service,一开始他们会让你感到困惑,这些是做什么的呢?

你的 IDE 很有可能提醒你,这个 module.ts 引用了你删除的文件,我们进入 module.ts 文件把他们也删掉就好了

这是删除后的结果

module.ts 文件非常关键,这类似我们 react 中的 app.js & app.ts 文件。这将是主模块或者其他模块将要导入的模块,你可以在官方文档的这个页面了解 module.ts 是做什么的,如果未来有类似的东西让你感到困惑,阅读文档即可!Modules | NestJS - A progressive Node.js framework
了解 module
在上面的文档大致了解一下 modules,回到代码
我们的 AppModule 上面写了一个 @Module()
,他是一个装饰器,你可以理解它将一些 metadata
添加到当前的类或者函数中

在官方文档中我们看到,一个 module 可以添加多个 modules,我们首先有一个 根模块 root module,然后导入了一些 user module,chat module 等等...

接下来我们以一个 书签 bookmarks 管理系统作为案例,设想一个书签管理系统需要什么功能 Feature ,显然我们需要一些身份验证系统(authentication )来鉴权,因此在我们的 crud api 中我们希望能够进行 创建、读取、删除、更新用户
这些操作,还要允许他们进行登录,对吧?
创建 auth module
一个最佳实践是,auth 相关的逻辑就在 src/auth 文件夹下进行(nestjs/cli 有一个功能可以帮助你创建这些 module,controller... 但今天我们手写一下来熟悉这个过程)

根据前面的约定,我们开始创建一个 auth.module.ts 在 auth 文件夹里 ~

接下来我们填写里面的内容,形成一个最简单的 module 结构(从 nestjs 引用装饰器 @Module,另外不要忘记把这个 class export 出去)

现在我们可以回到 app.module 导入 AuthModule 了

启动 NestJs
前面的工作一切顺利的话,接下来就可以尝试启动验证后端了,打开 main.ts 你会发现一些类似 express 的入口程序的代码

接下来打开终端,执行 pnpm start:dev
就可以了(npm run start:dev)

所有的 scripts 都是在 package.json 里面定义的,你可以注意到 start:dev
可以执行带 --watch 参数工程,这会在你修改文件的时候 重载服务器,方便开发

nestjs 生成了一个 dist 文件夹,这是 typescript 输出编译后的 javascript 文件,我们不需要关心他的工作原理

完善 auth module
回到 module,我们还需要一个 user 模块和 bookmarks 模块
使用 cli 生成 module
只需要打开终端输入 nest g module user
就可以生成 module user 了,g 意为 generate
创建 user module 之后还会自动导入

创建 auth controller & service
当我们构建一个后端项目,通常会把我们的逻辑分为 controller 和 service,我们可以参考文档进行学习,但大体上:
- controller 负责处理传入请求并返回响应
- provider/service 负责执行业务逻辑
这样做绝不是让事情更复杂,相反,会简化我们所关注事情的相关逻辑
和前面一样,我们手写一遍 controller 和 service

和 controller 不一样的点在于,service 使用 @Injectable()
装饰器,这代表它使用 nestjs 的 依赖注入

别忘记在 auth.module 里面声明,auth.module 使用了这些 controller 和 provider/service

写好了确认一下程序运行正常吗?一切顺利!

依赖注入 dependency injection
你可以尝试 google 知道这是什么东西,但在我们的项目中用一个例子来看看它是如何工作的吧
你有一个 auth.controller 还有一个 auth.service
controller 将收到来自 internet 的请求 request,然后调用 service 的函数,并将结果返回给客户端和浏览器
那么 auth.controller 就必须 实例化 一个 auth.service,对吧?
类似的 service declear 声明


为了 避免管理 AuthService 的 创建地点 和 创建者,我们使用依赖注入解决这个问题!
我们希望 auth.controller 得到一个 auth.service 的实例,不用管怎么去创建,怎么去管理它

这就是 nestjs 里面依赖注入的写法,框架会帮你处理好这些事情的

如果你不了解 private 的功能,需要注意到这里的 private 是个语法糖 short hand,帮你完成了这部分的工作

当我们完成了上面的工作之后,controller 就可以直接调用 this.authService 的内容了!你可以写一些属性、函数到 authService 里面进行测试
这就是依赖注入的核心内容,其他细节请自行探索,网上有很多解释 ~
初始化 controller
- 首先,在 auth.service 添加
login()
和signup()

- 添加装饰器方法 auth.controller,处理 Post 方法

- 因为我们在 auth 路径上,通常最好的做法是添加一个全局前缀路由 global prefix route,这里叫做 auth 就行

接下来在里面 return 一些字符串,进行测试 ~

- 让我们打开 postman/apifox 调一下接口看看吧,成功了!(这里的请求类型是 POST)

- 正如前面所说,auth.controller 被用来处理请求,auth.service 用来执行 server-side 业务逻辑,显然的,下面这样是正确的写法
这使得 controller 保持干净,只忙于请求的逻辑
也让 service 变得清晰,只忙于业务逻辑,比如连接到数据库,编辑字段等等
同样再去测试一次接口,也是可行的

使用数据库
如果没有数据库,上面的业务逻辑就没法更进一步了
接下来我们将引入 Docker 来运行数据库,Docker 可以让我们无需安装数据库,却把数据库跑起来!链接🔗Get Started | Docker
在终端输入 docker ps
就可以检查 docker 有没有安装成功了 ~
创建数据库
占坑,待补充
引入 ORM 框架
我们创建了像 mysql, postgresql, mongodb 这样的数据库,如何连接到他们呢?
一些 ORM 框架像 sequalize, mongoose 允许我们和数据库之间快捷稳定地建立联系,在这里我选择了一个新的 ORM 叫做 prisma
简而言之,它是一种 query builder,使用起来非常简单。建立一个 Model,包含一切你希望在数据库中保存的信息,就可以使用 typescript/javascript 来使用数据库里面的数据了 ~
我们需要安装 2 个库,一个是 prisma cli
,它允许我们将模型迁移到数据库,更像是一个维护库
另一个是 prisma client
用来访问数据
所以我们执行:
js
pnpm i prisma
pnpm i @prisma/client
npx prisma init
其中的 npx prisma init
被用来初始化项目的 prisma 配置
配置 prisma
初始化 prisma 配置后出现了如下 2 个新文件,其中的 .env
被用作数据库连接的配置文件
还有一个 prisma 文件夹,里面包含了一个 schema.prisma,我们在这里声明模型
如果你使用其他的 ORM 框架,你可能要创建一些 entity
来实现模型在业务逻辑的引用,但 prisma 只需要在 prisma 文件夹下声明模型就好了 ~

client 声明了我们使用了 prisma/client,前面我们已经安装了
db 声明了我们使用的 数据库 和 url ,这里我就使用 mysql 了
定义模型 model
我们现在有哪些模型?显然是 User 和 Bookmark 对吧
直接定义在里面就可以了!需要的字段可以由你自行定义,比较关键的是需要有一个字段被注明为 @default(autoincrement())

接下来我们执行 npx prisma migrate dev
可以看到模型同步到数据库了


在 migrate 执行的同时,prisma 会为你执行命令 npx prisma generate
,为你的项目自动创建 typescript 类型,我们就可以直接在代码里面使用 User
和 Bookmark
类型了!

还有一个很强的命令叫做 npx prisma studio
,他会帮你打开一个数据库查询页面,你可以直接在里面操作数据库

创建 module for prisma
到目前为止,我们成功创建了 prisma 架构,但我们还没有将 prisma 和 业务逻辑 连接起来,所以接下来我们要创建一个模块来连接 prisma
创建 module nest g module prisma
,然后创建 service nest g service prisma --no-spec
(我们不希望在这里有个测试文件)
一个不错的实践是让 prisma.service extends PrismaClient ,这是一个允许连接到数据库的类,有一些 connect、disconnect、execute SQL 等等功能
当然,我们也要实现父类的构造函数,这里的 url 参数从 .ENV 复制过来,暂时使用硬编码吧 ~

auth 引入 prisma
我们希望让 auth 使用 prisma.module 里面的功能,总先在 auth.module 里引入 prisma.module

然后我们的 auth.service 当然是会用到 prisma.service 的,所以在这里执行一个依赖注入

这时候你会发现 nest 在终端报错了!

报错的意思是,即使 auth.module 引入了 prisma.module,我仍然无法引入 prisma.service,为了解决这个问题,我们在 prisma.module 里面添加一个 exports 即可,这就解决了上面的问题

global 引入 prisma
我们的 user, auth, bookmark 模块都要用到 prisma,有没有一种可能可以全局引入 prisma 呢?
只需要在 prisma.module 加上 @Global()
装饰器就好了! 当然 exports 还是需要的
接下来只需要确保 global module 在 root module 被引入 import 了即可,不需要在每一个 module 里面进行 import 了

开始业务逻辑
上面的一切都搞定了,我们现在才真正开始编写业务逻辑,真正开始编程
配置这些确实有些乏味,但在 express 中实际上做这些事情则更复杂,在 nestjs 中我们有开箱即用的一切,长远来看将节约大多时间
request @user.controller
用户涉及到许多 authentication、jwt、web tokens 方面的知识
我们现在先重点关注 请求 和 请求中 发生的事情
首先我们调整一下 auth.controller 里面的逻辑,给 signup 加上一个带有装饰器的参数 req,这就是我们在 apifox 里面发的请求的数据了


下一章
更多内容补充在下一章节 ~