基于子域名动态配置数据库URL的NestJS + Prisma中间件实现
基于子域名动态配置Prisma数据库连接(NestJS多租户场景)
核心思路
利用NestJS的请求级依赖注入,结合中间件提取的子域名,动态生成对应租户的数据库连接字符串,并缓存Prisma客户端实例以优化性能。
实现步骤
1. 缓存租户Prisma客户端实例
创建全局缓存容器,避免重复创建PrismaClient(每个实例对应一个数据库连接池,重复创建会消耗过多资源):
// src/prisma/prisma-client.cache.ts import { PrismaClient } from '@prisma/client'; export const prismaClientCache = new Map<string, PrismaClient>();
2. 配置动态Prisma客户端提供者
在Prisma模块中,通过REQUEST对象获取子域名,生成对应数据库连接,并从缓存复用实例:
// src/prisma/prisma.module.ts import { Module, Scope } from '@nestjs/common'; import { REQUEST } from '@nestjs/core'; import { PrismaClient } from '@prisma/client'; import { prismaClientCache } from './prisma-client.cache'; @Module({ providers: [ { provide: PrismaClient, scope: Scope.REQUEST, // 请求级别作用域,确保每个请求匹配对应租户的客户端 useFactory: (req: any) => { const subdomain = req.subdomain; // 处理无子女域名的默认情况(如主域名连接默认数据库) if (!subdomain) { return new PrismaClient(); } // 优先从缓存获取实例 if (prismaClientCache.has(subdomain)) { return prismaClientCache.get(subdomain); } // 生成租户专属数据库URL(建议用环境变量模板替换,避免硬编码) const databaseUrl = `postgresql://${process.env.DB_USER}:${process.env.DB_PASS}@${process.env.DB_HOST}:${process.env.DB_PORT}/${subdomain}_db?schema=public`; const prisma = new PrismaClient({ datasources: { db: { url: databaseUrl } }, // 可选:自定义连接池配置 // poolSize: 10 }); prismaClientCache.set(subdomain, prisma); return prisma; }, inject: [REQUEST], }, ], exports: [PrismaClient], }) export class PrismaModule {}
3. 全局注册子域名提取中间件
确保中间件在所有请求前执行,将子域名挂载到请求对象:
// src/app.module.ts import { Module, MiddlewareConsumer } from '@nestjs/common'; import { DynamicDbMiddleware } from './middlewares/dynamic-db.middleware'; import { PrismaModule } from './prisma/prisma.module'; // 导入你的业务模块 import { UserModule } from './user/user.module'; @Module({ imports: [PrismaModule, UserModule], }) export class AppModule { configure(consumer: MiddlewareConsumer) { consumer.apply(DynamicDbMiddleware).forRoutes('*'); // 全局应用中间件 } }
4. 在业务服务中使用动态连接
直接注入PrismaClient即可,此时客户端已自动关联当前请求的租户数据库:
// src/user/user.service.ts import { Injectable } from '@nestjs/common'; import { PrismaClient } from '@prisma/client'; @Injectable() export class UserService { constructor(private readonly prisma: PrismaClient) {} async getTenantUsers() { // 这里的prisma已连接当前子域名对应的数据库 return this.prisma.user.findMany(); } }
注意事项
- 环境变量优化:建议将数据库URL模板存入环境变量(如
DATABASE_URL_TEMPLATE=postgresql://{user}:{pass}@{host}:{port}/{tenant}_db),通过字符串替换生成租户URL,避免硬编码敏感信息。 - 错误处理:需捕获数据库连接失败的异常(如租户数据库不存在),返回友好的错误响应。
- 缓存清理:如果租户可能被删除,需定期清理缓存中无效的PrismaClient实例,避免资源泄漏。
内容的提问来源于stack exchange,提问作者Abdulaziz mohammed
相关产品推荐
相关产品推荐

