NestJS:通过请求(子域)连接数据库(TypeORM) [英] NestJS : database connection (TypeORM) by request (subdomain)
问题描述
我正在尝试通过 Nest/TypeORM 构建 SAAS 产品,我需要按子域配置/更改数据库连接.
I'm trying to build a SAAS product over Nest/TypeORM and I need to configure/change database connection by subdomain.
customer1.domain.com => connect to customer1 database
customer2.domain.com => connect to customer2 database
x.domain.com => connect to x database
我该怎么做?使用拦截器或请求上下文(或 Zone.js)?
How can I do that ? With interceptors or request-context (or Zone.js) ?
我不知道如何开始.有人已经这样做了吗?
I don't know how to start. Is someone already do that ?
WIP:我目前在做什么:
WIP : what I am currently doing :
- 将所有连接设置添加到 ormconfig 文件中
在所有路由上创建中间件以将子域注入
res.locals
(实例名称)并创建/警告 typeorm 连接
- add all connections settings into ormconfig file
create Middleware on all routes to inject subdomain into
res.locals
(instance name) and create/warn typeorm connection
import { Injectable, NestMiddleware, MiddlewareFunction } from '@nestjs/common';
import { getConnection, createConnection } from "typeorm";
@Injectable()
export class DatabaseMiddleware implements NestMiddleware {
resolve(): MiddlewareFunction {
return async (req, res, next) => {
const instance = req.headers.host.split('.')[0]
res.locals.instance = instance
try {
getConnection(instance)
} catch (error) {
await createConnection(instance)
}
next();
};
}
}
在控制器中:从@Response 获取实例名称并将其传递给我的服务
in Controller : get instance name from @Response and pass it to my Service
@Controller('/catalog/categories')
export class CategoryController {
constructor(private categoryService: CategoryService) {}
@Get()
async getList(@Query() query: SearchCategoryDto, @Response() response): Promise<Category[]> {
return response.send(
await this.categoryService.findAll(response.locals.instance, query)
)
}
在服务中:通过 Repository 获取给定实例的 TypeORM Manager 和查询数据库
in Service : get TypeORM Manager for given instance and query database through Repository
@Injectable()
export class CategoryService {
// constructor(
// @InjectRepository(Category) private readonly categoryRepository: Repository<Category>
// ) {}
async getRepository(instance: string): Promise<Repository<Category>> {
return (await getManager(instance)).getRepository(Category)
}
async findAll(instance: string, dto: SearchCategoryDto): Promise<Category[]> {
let queryBuilder = (await this.getRepository(instance)).createQueryBuilder('category')
if (dto.name) {
queryBuilder.andWhere("category.name like :name", { name: `%${dto.name}%` })
}
return await queryBuilder.getMany();
}
它似乎有效,但我不确定几乎所有内容:
It seems to work but I not sure about pretty much everything :
- 连接池(我可以在 ConnectionManager 中创建多少个连接?)
- 将子域传递给 response.locals... 不好的做法?
- 可读性/理解性/添加大量额外代码...
- 副作用:我害怕在多个子域之间共享连接
- 副作用:性能
处理 response.send() + Promise + await(s) + 到处传递子域不是一件乐事...
It's not a pleasure to deals with response.send() + Promise + await(s) + pass subdomain everywhere...
有没有办法让子域直接进入我的服务?
Is there a way to get subdomain directly into my Service ?
有没有办法让正确的子域连接/存储库直接进入我的服务并将其注入我的控制器?
Is there a way to get correct subdomain Connection/Repository directly into my Service and Inject it into my Controller ?
推荐答案
我想出了另一个解决方案.
I came up with another solution.
我创建了一个中间件来获取特定租户的连接:
I created a middleware to get the connection for a specific tenant:
import { createConnection, getConnection } from 'typeorm';
import { Tenancy } from '@src/tenancy/entity/tenancy.entity';
export function tenancyConnection(...modules: Array<{ new(...args: any[]):
any; }>) {
return async (req, res, next) => {
const tenant = req.headers.host.split(process.env.DOMAIN)[0].slice(0, -1);
// main database connection
let con = ...
// get db config that is stored in the main db
const tenancyRepository = await con.getRepository(Tenancy);
const db_config = await tenancyRepository.findOne({ subdomain: tenant });
let connection;
try {
connection = await getConnection(db_config.name);
} catch (e) {
connection = await createConnection(db_config.config);
}
// stores connection to selected modules
for (let module of modules) {
Reflect.defineMetadata('__tenancyConnection__', connection, module);
}
next();
};
}
我将它添加到 main.ts 中:
I added it to the main.ts:
const app = await NestFactory.create(AppModule);
app.use(tenancyConnection(AppModule));
要访问连接,您可以通过以下方式扩展任何服务:
To access the connection you can extend any service by:
export class TenancyConnection {
getConnection(): Connection {
return Reflect.getMetadata('__tenancyConnection__', AppModule);
}
}
它仍然是一个草稿,但使用此解决方案,您可以在运行时为每个租户添加、删除和编辑连接.我希望这能进一步帮助您.
It is still a draft, but with this solution you can add, delete and edit the connection for each tenant at runtime. I hope this helps you further.
这篇关于NestJS:通过请求(子域)连接数据库(TypeORM)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!