NestJS 多租户数据库连接如何优雅透传?nestjs-cls 实战多租户上下文存储 📅 2026/8/24 8:43:07 NestJS 多租户数据库连接如何优雅透传nestjs-cls 实战多租户上下文存储【免费下载链接】nestjs-clsA continuation-local storage (async context) module compatible with NestJSs dependency injection.项目地址: https://gitcode.com/gh_mirrors/ne/nestjs-cls在 NestJS 多租户应用中让每个请求记住自己属于哪个租户、使用哪个数据库连接往往是架构里最头疼的一环。这篇文章带你使用nestjs-cls一个兼容 NestJS 依赖注入的异步上下文存储模块实现多租户数据库连接的优雅透传请求进来时存入租户标识业务层任何位置都能无感获取当前租户的连接不再需要在每个方法里层层手动传参。多租户数据库连接的三大痛点 假设你正在开发 SaaS 产品每个租户的数据都隔离在不同的数据库或不同 Schema中。传统的做法会遇到这些问题手动传参繁琐Controller → Service → Repository每一层都要显式传递tenantId或连接对象代码被污染REQUEST 作用域的陷阱把连接声明为请求作用域的 Provider会导致整个 DI 子树随请求重建性能差且无法在 WebSocket、定时任务等场景使用事务难以共享跨服务的事务引用如何传递显式传递会破坏封装隐式全局状态又容易串数据。这正是 README.md 中列出的核心场景之一Making the dynamic Tenant database connection available everywhere in multi-tenant apps让动态租户数据库连接在多租户应用中随处可用。先搞懂原理什么是 CLS 异步上下文CLSContinuation-Local Storage继续局部存储可以理解为请求级的线程局部存储请求到达时通过cls.run()建立一个上下文在上下文内用cls.set()/cls.get()读写数据同一请求回调链中的任何代码包括 Promise、异步函数深处都能访问到这份存储。它底层基于 Node.js 官方的AsyncLocalStorage天然支持异步传播且不改变任何 Provider 的作用域——这正是它比 REQUEST 作用域优雅的关键。原理详解可参考文档docs/docs/01_introduction/03_how-it-works.md。一键安装与模块注册3 步接入 ⚡安装方式与任意 NPM 包相同如需离线克隆源码仓库地址为 https://link.gitcode.com/i/f9884d2c1b5a8376dcce92c700034459npm install nestjs-cls然后在应用根模块注册挂载一个内置中间件即可全局生效ClsModule.forRoot({ middleware: { mount: true }, });完整安装与注册说明见docs/docs/01_introduction/01_installation.md和docs/docs/01_introduction/02_quick-start.md。核心技巧用 setup 钩子一行存入租户上下文 ✍️这是多租户透传的第一步在请求进入业务层之前把租户 ID 存进 CLS。setup钩子会在上下文建立后自动执行并拿到Request对象ClsModule.forRoot({ middleware: { mount: true, setup: (cls, req) { cls.set(TENANT_ID, req.params[tenantId]); }, }, });从此任意深度的 Service只需注入ClsService就能拿到租户 ID彻底告别层层传参。官方推荐的完整写法见docs/docs/03_features-and-use-cases/02_additional-cls-setup.md。 小技巧配合类型安全的 CLS Storethis.cls.get(TENANT_ID)会获得完整类型推断拼错 key 在编译期就会报错。详见docs/docs/03_features-and-use-cases/05_type-safety-and-type-inference.md。进阶利器Proxy Provider 动态解析租户连接 存了TENANT_ID之后怎么让每个租户的数据库连接自动注入到 Service答案Proxy Provider代理 Provider——这是 nestjs-cls 从 Spring 框架请求 Bean中汲取灵感的杀手级特性。它的巧妙之处在于注入的其实是一个单例 Proxy 对象不会污染宿主 Provider 的作用域每次请求时工厂函数根据当前 CLS 上下文比如TENANT_ID动态创建真正的连接实例存入上下文之后 Service 里像使用普通对象一样访问它底层自动路由到对应租户的连接。文档中正好给出了一个根据请求参数动态解析租户数据库连接的示例docs/docs/03_features-and-use-cases/06_proxy-providers.md工厂函数从请求中取出tenantId调用dbService.getTenantConnection(tenantId)返回连接之后任何注入该 token 的 Service 拿到的就是当前租户专属的连接。它还支持延迟解析通过resolveProxyProviders: false 手动cls.proxy.resolve()在上下文信息更完整时再解析严格模式strict: true上下文未就绪时访问会直接抛错避免静默返回空对象的隐蔽 Bug。事务也想透传交给 Transactional 插件 多租户场景中跨 Service 的数据库事务同样需要透传。官方nestjs-cls/transactional插件把事务引用也存进 CLS用TransactionHost.withTransaction()或Transactional()装饰器开启事务后续任何 Service 通过txHost.tx获取的就是同一个事务无需显式传参官方适配器覆盖Prisma、Knex、Kysely、TypeORM、Drizzle ORM、Pg-promise、MongoDB、Mongoose等主流库且无需 monkey-patch。源码位于packages/transactional/使用文档见docs/docs/06_plugins/01_available-plugins/01-transactional/index.md各适配器文档同在docs/docs/06_plugins/01_available-plugins/01-transactional/目录下。多租户落地的 4 条最佳实践清单 ✅统一入口写入租户标识只在中间件 / 拦截器的setup钩子中写入一次禁止散落在业务代码里用类型安全 Store为ClsStore声明tenantId等字段让 IDE 和编译器帮你守住数据隔离的底线连接交给 Proxy Provider保持 Provider 单例按需动态解析兼顾性能与隔离开启 strict 模式宁可快速失败也不要让未解析的代理静默返回空对象。总结nestjs-cls 的多租户上下文存储方案把租户 ID → 数据库连接 → 事务整条链路装进了一个请求级上下文中间件写入、Proxy Provider 动态解析、Transactional 插件透传事务三层组合拳让多租户数据库连接在整个 NestJS 应用中优雅透传——业务代码零侵入依赖注入习惯零改变。 延伸阅读项目内文档路径模块选项参考docs/docs/04_api/02_module-options.md非 Web 请求场景定时任务、队列docs/docs/03_features-and-use-cases/04_usage-outside-of-web-request.md核心服务实现packages/core/src/lib/cls.service.ts【免费下载链接】nestjs-clsA continuation-local storage (async context) module compatible with NestJSs dependency injection.项目地址: https://gitcode.com/gh_mirrors/ne/nestjs-cls创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考