NestJS:通过请求(子域)连接数据库(TypeORM) [英] NestJS : database connection (TypeORM) by request (subdomain)

查看:77
本文介绍了NestJS:通过请求(子域)连接数据库(TypeORM)的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试通过 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 :

  1. 将所有连接设置添加到 ormconfig 文件中
  2. 在所有路由上创建中间件以将子域注入 res.locals(实例名称)并创建/警告 typeorm 连接

  1. add all connections settings into ormconfig file
  2. 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屋!

    查看全文
    登录 关闭
    扫码关注1秒登录
    发送“验证码”获取 | 15天全站免登陆