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
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 : what I am currently doing :
- add all connections settings into ormconfig file
create Middleware on all routes to inject subdomain into
res.locals
(instance name) and create/warn typeorm connectionimport { 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(); }; } }
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) ) }
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 :
- connections poole (how many can I create connections into my ConnectionManager ?)
- pass subdomain into response.locals... bad practice ?
- readability / comprehension / adding lot of additional code...
- side effects : I'm afraid to share connections between several subdomains
- side effects : performance
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 ?
DOMAIN=customer1.domain.com node server.js
(if you're on linux). To use in your code useprocess.env.DOMAIN
– Victor Ivens