夜猫子的知识栈 夜猫子的知识栈
首页
  • 前端文章

    • JavaScript
  • 学习笔记

    • 《JavaScript教程》
    • 《Web Api》
    • 《ES6教程》
    • 《Vue》
    • 《React》
    • 《TypeScript》
    • 《Git》
    • 《Uniapp》
    • 小程序笔记
    • 《Electron》
    • JS设计模式总结
  • 《前端架构》

    • 《微前端》
    • 《权限控制》
    • monorepo
  • 全栈项目

    • 任务管理日历
    • 无代码平台
    • 图书管理系统
  • HTML
  • CSS
  • Nodejs
  • Midway
  • Nest
  • MySql
  • 其他
  • 技术文档
  • GitHub技巧
  • 博客搭建
  • Ajax
  • Vite
  • Vitest
  • Nuxt
  • UI库文章
  • Docker
  • 学习
  • 面试
  • 心情杂货
  • 实用技巧
  • 友情链接
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

夜猫子

前端练习生
首页
  • 前端文章

    • JavaScript
  • 学习笔记

    • 《JavaScript教程》
    • 《Web Api》
    • 《ES6教程》
    • 《Vue》
    • 《React》
    • 《TypeScript》
    • 《Git》
    • 《Uniapp》
    • 小程序笔记
    • 《Electron》
    • JS设计模式总结
  • 《前端架构》

    • 《微前端》
    • 《权限控制》
    • monorepo
  • 全栈项目

    • 任务管理日历
    • 无代码平台
    • 图书管理系统
  • HTML
  • CSS
  • Nodejs
  • Midway
  • Nest
  • MySql
  • 其他
  • 技术文档
  • GitHub技巧
  • 博客搭建
  • Ajax
  • Vite
  • Vitest
  • Nuxt
  • UI库文章
  • Docker
  • 学习
  • 面试
  • 心情杂货
  • 实用技巧
  • 友情链接
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • Node基础

  • 《MySQL》学习笔记

  • Midway

  • Nest

    • 开篇词
    • 学习理由
    • nest概念扫盲
    • 快速掌握 nestcli
    • 5种http数据传输方式
    • IoC 解决了什么痛点问题?
    • 如何调试 Nest 项目
    • Provider注入对象
    • 全局模块和生命周期
    • AOP 架构有什么好处?
    • 一网打尽 Nest 全部装饰器
    • Nest如何自定义装饰器
    • Metadata和Reflector
    • ExecutionContext切换上下文
    • Module和Provider的循环依赖处理
    • 如何创建动态模块
    • Nest和Express,fastify
    • Nest的Middleware
    • RxJS和Interceptor
    • 内置Pipe和自定义Pipe
    • ValidationPipe验证post请求参数
    • 如何自定义 Exception Filter
    • 图解串一串 Nest 核心概念
    • 接口如何实现多版本共存
    • Express如何使用multer实现文件上传
    • Nest使用multer实现文件上传
    • 图书管理系统
    • 大文件分片上传
    • 最完美的 OSS 上传方案
    • Nest里如何打印日志
    • 为什么Node里要用Winston打印日志
    • Nest 集成日志框架 Winston
    • 通过Desktop学Docker也太简单了
    • 你的第一个 Dockerfile
    • Nest 项目如何编写 Dockerfile
    • 提升 Dockerfile 水平的 5 个技巧
    • Docker 是怎么实现的
    • 为什么 Node 应用要用 PM2 来跑?
    • 快速入门 MySQL
    • SQL 查询语句的所有语法和函数
    • 一对一、join 查询、级联方式
    • 一对多、多对多关系的表设计
    • 子查询和 EXISTS
    • SQL 综合练习
    • MySQL 的事务和隔离级别
    • MySQL 的视图、存储过程和函数
    • Node 操作 MySQL 的两种方式
    • 快速掌握 TypeORM
    • TypeORM 一对一的映射和关联 CRUD
    • TypeORM 一对多的映射和关联 CRUD
    • TypeORM 多对多的映射和关联 CRUD
    • 在 Nest 里集成 TypeORM
      • 先回忆下 TypeORM 的流程:
      • 那如果让你把 TypeORM 的 api 封装一层,做成一个 TypeOrmModule,你会怎么封装呢?
      • 那我们来看看 @nestjs/typeorm 是怎么封装的吧。
      • 🚀我们引入 typeorm 来实现下:
        • 连接配置和前几节一样,引入 User 的 Entity。
        • 然后在 User 的 Entity 里加一些映射的信息:
        • 然后是增删改查,我们可以注入 EntityManager:
        • 简便方法就是先 getRepository(User) 拿到 user 对应的 Repository 对象,再调用这些方法。
        • 此外,你还可以注入 DataSource:
      • 那它是怎么实现的呢?
      • 总结
    • TypeORM保存任意层级的关系
    • 生产环境为什么用TypeORM的migration迁移功能
    • Nest 项目里如何使用 TypeORM 迁移
    • 如何动态读取不同环境的配置?
    • 快速入门 Redis
    • 在 Nest 里操作 Redis
    • 为什么不用 cache-manager 操作 Redis
    • 两种登录状态保存方式:JWT、Session
    • Nest 里实现 Session 和 JWT
    • MySQL + TypeORM + JWT 实现登录注册
    • 基于 ACL 实现权限控制
    • 基于 RBAC 实现权限控制
    • access_token和refresh_token实现无感登录
    • 单token无限续期实现登录无感刷新
    • 使用 passport 做身份认证
    • passport 实现 GitHub 三方账号登录
    • passport 实现 Google 三方账号登录
  • 其他

  • 服务端
  • Nest
神说要有光
2025-03-10
目录

在 Nest 里集成 TypeORM

TypeORM 怎么用我们已经学会了,在 Nest 里用那不就是再封装一层的事情么?

那怎么封装呢?

# 先回忆下 TypeORM 的流程:

1.DataSource 里存放着数据库连接的配置,比如用户名、密码、驱动包、连接池配置等等。

2.而 Entity 里通过 @Entity、@PrimaryGeneratedColumn、@Column 等装饰器来建立数据库表的映射关系。

3.同时还有 Entity 之间的 @OneToOne、@OneToMany、@ManyToMany 的关系,这些会映射成数据库表通过外键、中间表来建立的关系。

4.DataSource.initialize 的时候,会和数据库服务建立连接,如果配置了 synchronize,还会生成建表 sql 语句来创建表。

5.DataSource 初始化之后就可以拿到 EntityManager 了,由它负责对各种 Entity 进行增删改查,比如 find、delete、save 等方法,还可以通过 query builder 来创建复杂的查询。

6.如果你只是想做对单个 Entity 的 CRUD,那可以拿到这个 Entity 的 Repository 类,它同样有上面的那些方法,只是只能用来操作单个 Entity。

这就是 TypeORM 的流程。

# 那如果让你把 TypeORM 的 api 封装一层,做成一个 TypeOrmModule,你会怎么封装呢?

很明显,这里的 datasource 的配置是需要手动传入的,也就是说需要做成动态模块,支持根据传入的配置来动态产生模块内容。

而动态模块的规范里就 3 种方法名: register、forRoot、forFeature。

这里很明显要用 forRoot,也就是只需要注册一次,然后这个模块会在各处被使用。

类似这样:

import { Module } from "@nestjs/common";
import { TypeOrmModule } from "xxx";

@Module({
  imports: [
    TypeOrmModule.forRoot({
      type: "mysql",
      host: "localhost",
      port: 3306,
      username: "root",
      password: "root",
      database: "test",
      entities: [],
      synchronize: true,
    }),
  ],
})
export class AppModule {}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

然后内部就根据传入的配置来创建 DataSource,调用 intialize 方法,之后就拿到 EntityManager,可以做 CRUD 了。

但是 Entity 肯定会分散在各个业务模块,每个模块都通过 forRoot 引入那个模块太麻烦,我们干脆把它用 @Global 声明成全局的。

这样每个模块里就都可以注入 EntityManager 来用了,不需要 imports。

那如果我想用 Repository 的方式来 CRUD 呢?

那可以先注入 EntityManager,然后再通过 EntityManager.getRepository(XxxEntity) 来拿呀。

或者可以再做一个动态模块,传入 Entity,返回它的 Repository。

这种局部的动态模块,一般都是用 forFeature 的名字:

import { Module } from "@nestjs/common";
import { TypeOrmModule } from "xxxx";
import { UsersService } from "./users.service";
import { User } from "./user.entity";

@Module({
  imports: [TypeOrmModule.forFeature([User])],
  providers: [UsersService],
  controllers: [],
})
export class UsersModule {}
1
2
3
4
5
6
7
8
9
10
11

比如传入 User,内部通过 EntityManager.getRepository(User) 来拿到 UserEntity。

这样 UserService 里就可以通过 UserRepository 来实现增删改查了。

这个封装思路貌似挺完美。

# 那我们来看看 @nestjs/typeorm 是怎么封装的吧。

创建个 Nest 项目:

nest new nest-typeorm -p npm
1

然后创建一个 crud 的模块:

nest g resource user
1

生成的 service 里的 crud 并没有真正实现:

# 🚀我们引入 typeorm 来实现下:

npm install --save @nestjs/typeorm typeorm mysql2
1

typeorm、mysql2 的包我们很熟悉了,而 @nestjs/typeorm 就是把 typeorm api 封装了一层的包。

它提供了一个模块,我们在入口引入下:

# 连接配置和前几节一样,引入 User 的 Entity。

import { Module } from "@nestjs/common";
import { TypeOrmModule } from "@nestjs/typeorm";
import { AppController } from "./app.controller";
import { AppService } from "./app.service";
import { User } from "./user/entities/user.entity";
import { UserModule } from "./user/user.module";

@Module({
  imports: [
    UserModule,
    TypeOrmModule.forRoot({
      type: "mysql",
      host: "localhost",
      port: 3306,
      username: "root",
      password: "guang",
      database: "typeorm_test",
      synchronize: true,
      logging: true,
      entities: [User],
      poolSize: 10,
      connectorPackage: "mysql2",
      extra: {
        authPlugin: "sha256_password",
      },
    }),
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

# 然后在 User 的 Entity 里加一些映射的信息:

import { Column, Entity, PrimaryGeneratedColumn } from "typeorm";

@Entity({
  name: "aaa_user",
})
export class User {
  @PrimaryGeneratedColumn()
  id: number;

  @Column({
    name: "aaa_name",
    length: 50,
  })
  name: string;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

给映射的表给个名字叫 aaa_user,然后有两个字段,分别是 id 和 name。

我们跑一下试试:

npm run start:dev
1

看到建表 sql 了没:

这部分和我们单独跑 typeorm 没啥区别:

# 然后是增删改查,我们可以注入 EntityManager:

用它来做增删改查:

import { Injectable } from '@nestjs/common';
import { InjectEntityManager } from '@nestjs/typeorm';
import { EntityManager } from 'typeorm';
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';
import { User } from './entities/user.entity';

@Injectable()
export class UserService {

  @InjectEntityManager()
  private manager: EntityManager;

  create(createUserDto: CreateUserDto) {
    this.manager.save(User, createUserDto);
  }

  findAll() {
    return this.manager.find(User)
  }

  findOne(id: number) {
    return this.manager.findOne(User, {
      where: { id }
    })
  }

  update(id: number, updateUserDto: UpdateUserDto) {
    this.manager.save(User, {
      id: id,
      ...updateUserDto
    })
  }

  remove(id: number) {
    this.manager.delete(User, id);
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

这里的 save、findOne、delete 方法我们都用过。

然后我们用 postman 来试一下:

发个 post 请求,带上要添加的数据:

服务端打印了 insert 的 sql 语句:

表里也可以看到这条数据了:

对应的是这个 handler:

然后再试下查询:

单个查询和全部查询都是可以的。

再就是修改:

在 controller 里是接受 patch 的请求。

在 postman 里发一下:

可以看到生成了 update 的 sql 语句:

数据库中的数据也被修改了:

再试试删除:

在 postman 里发送 delete 的请求:

可以看到生成了 delete 的 sql 语句:

数据库里的数据确实被删除了:

至此,我们就正式打通了从请求到数据库的整个流程!

这里的 CRUD 部分用到的 api 我们都用过好多遍了。

只不过现在是通过 TypeOrm.forRoot 来传入的数据源的配置,通过 @InjectEntityManager 来注入的 entityManager 对象。

直接用 EntityManager 的缺点是每个 api 都要带上对应的 Entity:

# 简便方法就是先 getRepository(User) 拿到 user 对应的 Repository 对象,再调用这些方法。

比如这样:

那还不如直接注入 User 对应的 Respository 就好了。

Nest 对这个做了封装,在 user 模块引入 TypeOrmModule.forFeature 对应的动态模块,传入 User 的 Entity:

就可以在模块里注入 Repository 了:

它有的方法和 EntityManager 一样,只是只能用来操作当前 Entity。

# 此外,你还可以注入 DataSource:

不过这个不常用。

这就是 Nest 里集成 TypeOrm 的方式。

有了 TypeOrm 的使用基础之后,学起来还是非常简单的。

# 那它是怎么实现的呢?

我们来看下源码:

首先,我们通过引入 TypeOrmModule.forRoot 的动态模块的时候:

它会引入 TypeOrmCoreModule.forRoot 的动态模块:

这里面根据 options 创建 DataSource 和 EntityManager 放到模块的 provider 里,并放到了 exports 里。

而且,更重要的是这个模块是 @Global 的全局模块。

因此,dataSource 和 entityManager 就可以在任意的地方注入了。

上面那两个方法里,创建 DataSource 的过程就是传入参数,调用 intialize 方法:

而创建 entityManager,则是注入 dataSource 取 manager 属性就好了:

然后 TypeOrmModule.forFeature 则是通过全局的 dataSource.getRepository 拿到参数对应的 Repository 对象,作为模块内的 provider。

这样引入这个动态模块的模块内就可以注入这些 Entity 对应的 Repository 了。

这就是 @nestjs/typeorm 的 TypeOrmModule.forRoot 和 TypeOrmModule.forFeature 的实现原理。

案例代码在小册仓库 (opens new window)。

# 总结

我们会了用 TypeOrm 来连接和增删改查数据库表,在 Nest 里集成只是对 TyprOrm 的 api 封装了一层。

使用方式是在根模块 TypeOrmModule.forRoot 传入数据源配置。

然后就可以在各处注入 DataSource、EntityManager 来做增删改查了。

如果想用 Repository 来简化操作,还可以在用到的模块引入 TypeOrmModule.forFeature 的动态模块,传入 Entity,会返回对应的 Repository。

这样就可以在模块内注入该 Repository 来用了。

它的原理是 TypeOrmModule.forRoot 对应的动态模块是全局的,导出了 dataSource、entityManager,所以才可以到处注入。

而 TypeOrmModule.forFeature 则会根据吧传入 Entity 对应的 Repository 导出,这样就可以在模块内注入了。

这就是 Nest 里集成 TypeOrm 的方式和实现原理。

至此,我们就可以打通从请求到数据库的流程了。

编辑 (opens new window)
上次更新: 2025/9/9 09:58:02
TypeORM 多对多的映射和关联 CRUD
TypeORM保存任意层级的关系

← TypeORM 多对多的映射和关联 CRUD TypeORM保存任意层级的关系→

最近更新
01
IoC 解决了什么痛点问题?
03-10
02
如何调试 Nest 项目
03-10
03
Provider注入对象
03-10
更多文章>
Copyright © 2019-2025 Study | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式