前端进阶之旅前端进阶之旅
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版

Nestjs 学习总结 从控制器到 TypeORM 的后端实战笔记

首页2022-05-25 20:35:24Front-End
NodeNestTypeORM

前端写久了总想把一个接口从头到尾自己做完。Express 上手最快,可写到第三个模块就开始难受,路由、参数校验、数据库连接、异常处理各写各的,没有统一约定,换个人接手得重读一遍代码才敢改。NestJS 给的正是这层约定,控制器收请求,服务干活,模块负责组装,中间还塞了五种切面能力去接管横切逻辑。这篇是我把 NestJS 从建项目一路做到接 MySQL 和 Redis 之后攒下来的完整笔记,配置、代码、踩坑点都在里面,需要哪块直接翻到哪块抄走。

在本篇文章中,我们将从浅入深,和大家一起学习以下知识:

  • NestJS 的项目结构,控制器、服务、模块分别在解决什么问题
  • 静态资源、模板引擎、Cookie 与 Session 这几个 Express 老熟人在 Nest 里怎么配
  • 中间件、守卫、拦截器、管道、异常过滤器的职责边界和真实执行顺序
  • 用 DTO 配合 class-validator 做参数校验,把校验规则从业务代码里摘出去
  • 配置抽离、多环境变量、文件上传下载、图片验证码、邮件服务、定时任务
  • passport + JWT 登录鉴权、密码加密方案、RBAC 角色权限的表设计和守卫实现
  • 接入 Swagger 自动生成可调试的接口文档
  • MongoDB 与 TypeORM 操作 MySQL,实体设计、五种访问方式、三种增删改查写法
  • 事务的三种用法,一对一、一对多、多对多关系怎么设计和增删改查
  • 接入 Redis,以及用它实现单点登录

先把 Nest 是个什么东西说清楚。

Nest (NestJS) 是一个用于构建高效、可扩展的 Node.js 服务器端应用程序的开发框架。它利用 JavaScript 的渐进增强的能力,使用并完全支持 TypeScript (仍然允许开发者使用纯 JavaScript 进行开发),并结合了 OOP (面向对象编程)、FP (函数式编程)和 FRP (函数响应式编程)。

  • 在底层,Nest 构建在强大的 HTTP 服务器框架上,例如 Express (默认),并且还可以通过配置从而使用 Fastify !
  • Nest 在这些常见的 Node.js 框架 (Express/Fastify) 之上提高了一个抽象级别,但仍然向开发者直接暴露了底层框架的 API。这使得开发者可以自由地使用适用于底层平台的无数的第三方模块。

我一直觉得 Nest 最值钱的地方不是某个 API,而是它把「一个后端项目该怎么分层」这件事变成了框架级别的强制约定。你写了三个月,别人接手也知道去哪找登录逻辑。

本文基于 nest8 演示。这里必须提前说一句,这篇写于 2022 年 5 月,之后 NestJS 又发过大版本,TypeORM 也从 0.2 升到了 0.3,一些 API 的签名有变化,最典型的就是 findOne 从接受裸 id 改成必须传 where 对象、Connection 相关的一批全局函数被重新组织过。下文我把原始写法原样保留了,因为很多人手上的老项目还跑在这套 API 上,但如果你是新起项目,装完包之后请以官方文档的当前版本为准,不要照抄我这里的旧签名。

# 一、基础篇 项目结构与请求生命周期

这一部分解决的是「怎么把一个 Nest 项目跑起来并且组织好」。控制器、服务、模块是三个必须先分清的角色,之后的静态资源、模板引擎、Cookie 和 Session 都是把 Express 的能力接进 Nest 的写法,最后那一大块中间件守卫管道过滤器拦截器,是 Nest 区别于裸 Express 最核心的东西。

# 创建项目

Nest 的 CLI 不是可选项,是这个框架的一部分。手写目录结构当然也行,但控制器、服务、模块、DTO 之间有一套固定的文件命名和注册关系,用 CLI 生成能省掉一堆「忘了在 module 里注册」的低级错误。所以第一步先全局装它。

$ npm i -g @nestjs/cli
@前端进阶之旅: 代码已经复制到剪贴板

nest new project-name 创建一个项目

$ tree
.
├── README.md
├── nest-cli.json
├── package.json
├── src
│   ├── app.controller.spec.ts
│   ├── app.controller.ts
│   ├── app.module.ts
│   ├── app.service.ts
│   └── main.ts
├── test
│   ├── app.e2e-spec.ts
│   └── jest-e2e.json
├── tsconfig.build.json
└── tsconfig.json

2 directories, 12 files
@前端进阶之旅: 代码已经复制到剪贴板

目录很干净,真正要看的就三个文件。

以下是这些核心文件的简要概述:

  • app.controller.ts 带有单个路由的基本控制器示例。
  • app.module.ts 应用程序的根模块。
  • main.ts 应用程序入口文件。它使用 NestFactory 用来创建 Nest 应用实例。

main.ts 包含一个异步函数,它负责引导我们的应用程序:

import { NestFactory } from '@nestjs/core';
import { ApplicationModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(ApplicationModule);
  await app.listen(3000);
}
bootstrap();
@前端进阶之旅: 代码已经复制到剪贴板
  • NestFactory 暴露了一些静态方法用于创建应用实例
  • create() 方法返回一个实现 INestApplication 接口的对象, 并提供一组可用的方法

main.ts 这个 app 实例后面会被反复用到。全局管道、全局守卫、全局过滤器、静态资源目录、Swagger 挂载,全都是在这里往 app 上挂,所以看一个 Nest 项目,先翻 main.ts 基本就知道它开了哪些全局能力。

底层跑的是谁也可以换。

nest有两个支持开箱即用的 HTTP 平台:express 和 fastify。 您可以选择最适合您需求的产品

  • platform-express Express 是一个众所周知的 node.js 简约 Web 框架。 这是一个经过实战考验,适用于生产的库,拥有大量社区资源。 默认情况下使用 @nestjs/platform-express 包。 许多用户都可以使用 Express ,并且无需采取任何操作即可启用它。
  • platform-fastify Fastify 是一个高性能,低开销的框架,专注于提供最高的效率和速度。

这里有个坑要注意。选 Express 还是 Fastify,会影响到后面 Cookie、Session、静态资源、文件上传这些能力的写法,因为它们说到底就是在调底层平台的 API。下文所有例子都是 Express 平台的写法,如果你换了 Fastify,对应的中间件包和调用方式要跟着换。我自己只在 Express 平台上完整跑过,Fastify 那条线没验证过。

# Nest控制器

Nest中的控制器层负责处理传入的请求, 并返回对客户端的响应。

下面这张图是 Nest 官方对控制器位置的示意,客户端请求先落到控制器,控制器再决定交给谁处理。

Nest 控制器在请求链路中的位置示意图

控制器的目的是接收应用的特定请求。路由机制控制哪个控制器接收哪些请求。通常,每个控制器有多个路由,不同的路由可以执行不同的操作

有一条纪律建议一开始就守住,控制器里不写业务逻辑。它只干三件事,声明路由、从请求里取参数、把结果 return 出去。查库、算数据、调第三方全部丢给 service。这条守住了,后面写单元测试和换数据源的时候会轻松很多。

通过NestCLi创建控制器:

nest -h 可以看到nest支持的命令

常用命令:

  • 创建控制器:nest g co user module
  • 创建服务:nest g s user module
  • 创建模块:nest g mo user module
  • 默认以src为根路径生成

执行 nest -h 之后能看到完整的命令列表,长这样。

nest -h 输出的 CLI 命令列表

实际用起来,比如要建一个文章模块的控制器。

nest g controller posts
@前端进阶之旅: 代码已经复制到剪贴板

表示创建posts的控制器,这个时候会在src目录下面生成一个posts的文件夹,这个里面就是posts的控制器,代码如下

import { Controller } from '@nestjs/common';

@Controller('posts')
export class PostsController {
}
@前端进阶之旅: 代码已经复制到剪贴板

创建好控制器后,nestjs会自动的在 app.module.ts 中引入PostsController,代码如下

// src/app.module.ts
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { PostsController } from './posts/posts.controller'

@Module({
    imports: [],
    controllers: [AppController, PostsController],
    providers: [AppService],
})
export class AppModule {}
@前端进阶之旅: 代码已经复制到剪贴板

CLI 自动改 app.module.ts 这个行为很关键。Nest 的依赖注入是靠模块元数据串起来的,控制器不写进 controllers 数组就不会被扫描到,路由自然也不存在。手写文件最容易漏的就是这一步。

# nest配置路由请求数据

路由声明完,下一个问题就是怎么把请求里的数据拿出来。Express 里是从 req.query、req.body、req.params 上手动抠,Nest 把这套包成了参数装饰器,写在形参上,框架帮你注进来。

Nestjs提供了其他HTTP请求方法的装饰器 @Get() @Post() @Put() 、 @Delete()、 @Patch()、 @Options()、 @Head()和 @All()

在Nestjs中获取Get传值或者Post提交的数据的话我们可以使用Nestjs中的装饰器来获取。

左边是 Nest 的装饰器,右边是它对应到 Express 原生对象上的哪个字段,对照着看就很清楚了。

@Request()  req
@Response() res
@Next() next
@Session()  req.session
@Param(key?: string)    req.params / req.params[key]
@Body(key?: string) req.body / req.body[key]
@Query(key?: string)    req.query / req.query[key]
@Headers(name?: string) req.headers / req.headers[name]
@前端进阶之旅: 代码已经复制到剪贴板

示例

fe
  • 一、基础篇 项目结构与请求生命周期
    • 创建项目
    • Nest控制器
    • nest配置路由请求数据
    • Nest服务
    • Nest模块
    • 配置静态资源
    • 配置模板引擎
    • Cookie的使用
    • Session的使用
    • 跨域,前缀路径、网站安全、请求限速
    • 管道、守卫、拦截器、过滤器、中间件
      • 管道
      • 守卫
      • 装饰器
      • 拦截器
      • 过滤器
      • 中间件
    • 一例看懂中间件、守卫、管道、异常过滤器、拦截器
      • 中间件是请求的第一道关卡
      • 守卫是第二道关卡
      • 拦截器是第三道关卡
      • 管道是第四道关卡
    • 数据验证
  • 二、进阶篇 配置、鉴权、权限与接口文档
    • 配置抽离
    • 环境配置
    • 文件上传与下载
    • 实现图片随机验证码
    • 邮件服务
    • nest基于passport + jwt做登陆验证
    • 对数据库的密码加密:md5和bcryptjs
    • 角色权限
      • RBAC
      • 基于RBAC的设计
      • 数据库实体设计
      • 接口实现
      • 后端的权限访问
    • 定时任务
    • 接入Swagger接口文档
  • 三、数据库篇 MongoDB、TypeORM 与 Redis
    • nest连接Mongodb
    • typeORM操作Mysql数据库
    • nest统一处理数据库操作的查询结果
    • 数据库实体设计与操作
      • 实体设计
      • 抽离部分重复的字段:使用继承
      • 实体监听装饰器
    • typeorm增删改查操作
      • 多种访问数据库的方式
      • 增删改查的三种方式
    • typeorm使用事务的3种方式
    • typeorm 一对一关系设计与增删改查
    • typeorm 一对多和多对一关系设计与增删改查
    • typeorm 多对多关系设计与增删改查
      • 实体设计
      • 多对多增删改查
    • nest连接Redis
    • 集成redis实现单点登录
  • 四、常见问题
    • Q:nestJS注入其他依赖时为什么还需要导入其module
    • Q:循环依赖怎么办
    • Q:为什么全局守卫里注入的依赖是 undefined
    • Q:数据存进去查出来差了 8 小时
  • 总结
  • 参考

← Egg 项目部署实战 docker-compose 微信云托管 serverless 三种方案一次node文件操作过多排查总结 EMFILE too many open files →