5

Looking to implement a multi-tenant NestJS solution using the new request injection scope feature of NestJS 6.

For any given service I assume I could do something like this:

@Injectable({scope: Scope.REQUEST})
export class ReportService implements OnModuleInit { ... }

then, in the constructor, determine the tenant from the request, connect to the appropriate database, and instantiate repositories for the new connection.

I'm wondering if this is the most straightforward way to go about it?

Instead of updating each service, is it possible to override the connection provider and scope that to the request?

nurikabe
  • 3,802
  • 2
  • 31
  • 39

3 Answers3

12

Here's what we ended up doing...

  1. Create a simple, global TenancyModule bound to the request scope:

tenancy.module.ts

import { Global, Module, Scope } from '@nestjs/common';
import { REQUEST } from '@nestjs/core';
import { getConnection } from 'typeorm';

const connectionFactory = {
  provide: 'CONNECTION',
  scope: Scope.REQUEST,
  useFactory: (req) => {
    const tenant = someMethodToDetermineTenantFromHost(req.headers.host);
    return getConnection(tenant);
  },
  inject: [REQUEST],
};

@Global()
@Module({
  providers: [connectionFactory],
  exports: ['CONNECTION'],
})
export class TenancyModule {}
  1. Inject request-specific 'CONNECTION' into module services from which to retrieve repositories:

user.service.ts

...
@Injectable({scope: Scope.REQUEST})
export class UserService {
  private readonly userRepository: Repository<User>;

  constructor(@Inject('CONNECTION') connection) {
    this.userRepository = connection.getRepository(User);
  }
nurikabe
  • 3,802
  • 2
  • 31
  • 39
  • 1
    This works for Multi-tenant with multiple databases, not for one database with multiple schemas – jdnichollsc Sep 29 '19 at 02:41
  • I am missing part about the connection itself, would you be able to provide example when do you pass database connection details? E.g. tenant = different database in the same host. @nurikabe – WinterTime Oct 10 '19 at 01:10
  • 1
    @jdnichollsc, it also works with multiple schemas as you can also change the schema when setting up a connection. – Felix K. Nov 07 '19 at 11:05
  • 1
    @WinterTime you pass all the database connection details when calling getConnection, see the TypeORM API docs. In the code by nurikabe, it seems he is just passing the tenant id "string", which would be wrong, he needs to pass the whole connection configuration here where e.g. the scheme or db name is set to the tenant.. – Felix K. Nov 07 '19 at 11:06
  • Ohh you're right! I don't know the impact about performance updating the connection at runtime using Request scope, but it can works :) – jdnichollsc Nov 07 '19 at 14:19
  • Unless I remember incorrectly, the above *should* return the entire connection object. Note the factory: It returns `getConnection(tenant)`. `'CONNECTION'` is an alias that returns the result of `connectionFactory`. – nurikabe Nov 07 '19 at 16:24
  • Ah yes, sorry @nurikabe, you are right. I confused `getConnection` with `createConnection`. So for retrieving the connection by name your `getConnection(tenant)` is perfectly correct. – Felix K. Nov 08 '19 at 12:52
  • @jdnichollsc When using `createConnection` (to create a connection for a tenant) TypeORM behind the curtains stores the connection instance and subsequent calls to `getConnection` (and the factory) will return the same connection instance again, so you only create the connection once and not on every request. So you only create one connection per tenant and not one per request. However, you may experience memory problems with too many connections on a single server and unfortunately switching the schema for existing connections doesn't work (see https://stackoverflow.com/q/57459643/2477619) – Felix K. Nov 08 '19 at 13:06
  • 1
    @B12Toaster at what point do I have to have all connection settings loaded to call getConnection (tenat) to get the connection by name. I did not understand this part – Ger Apr 23 '20 at 21:41
  • @EdeGerSil I think you could create the multiple connections in the app module, look at this link, https://stackoverflow.com/a/51995362/11434648 – ontimond Jul 14 '20 at 19:09
3

I would recommend to use the approach by @nurikabe with a request scoped factory provider and request scoped services. Nestjs itself has a similar factory example in the docs.

But for the sake of completenes, there is also another approach: You could also use a middleware and attach the connection to the request object as described in this answer to a similar question. However, attaching things like a connection to the request via a middleware is circumventing the DI mechanism and alienates the request object by making it behave like a service container that delivers the connection – therefore the factory approach should be preferred.

Felix K.
  • 14,171
  • 9
  • 58
  • 72
0

It is preferable to inject the connection as a provider (coming from a factory) rather than attaching it to the request.

Note that both approaches will inevitably lead to an increase in the number of connections being created. This can cause performance issues, even with connections pooling. For this reason, such an approach (one connection per tenant) is only really efficient when the number of tenants is relatively low.

One way to do it with a multi-schema approach is fully documented in this article.