Note: the async_hooks feature is still in stability state 1. Thus, no backwards compatibility or guaranteed integrity with npm is guaranteed for future releases. Use with caution!

In the post Multi Tenancy with Nestjs I explained an approach using NestJS factory providers. As this changes the scope of all dependent classes to REQUEST, it may lead to a major performance decrease. Each request would generate new instances of each class in injection chains which have the Tenant injected.

Using NestJS' async_hooks rather than a provider factory might solve that problem. Async_hooks can be used to track asynchronous resources, and will help with tracking the current tenant therefore. One might see such an async_hook resources as a session or namespace to which NodeJS can push key-value information. That information can then be accessed from everywhere within the application.

To have a general entry point, I suggest using a NestJS Middleware which extracts the Tenant from each REQUEST and stores it in an async_hooks resource.

architecture

NestJS middleware

A middleware in Nestjs is a function which has access to the request and response objects. It is called before the route handler and the developer decides whether it is applied globally or only to specific routes. Additionally, middleware supports full dependency injection.

Regarding our example project from the original post, we’ll remove the tenant factory and all its references in the Tenant module. Next, we’ll create a class providing our middleware function.

import { Injectable, NestMiddleware } from "@nestjs/common";

@Injectable()
export class TenantMiddleware implements NestMiddleware {
  use(): any {
    next();
  }
}

The next() function calls the method of the route which was invoked originally. If next() is removed, the request will not be passed to the controller. To execute the middleware before a route, we can either use app.use(functionName) if we have a single middleware function or, like in our case, attach it as a consumer within a module:

import { TenantMiddleware } from './common/middleware/tenant.middleware';
[..]
export class AppModule {
  configure(consumer: MiddlewareConsumer): void {
   
    consumer
      .apply(TenantMiddleware)
      .forRoutes({ path: "*", method: RequestMethod.ALL });
  }
}

Great! Now, each request to the application passes through the middleware before reaching its related route in the controller.

aync_hooks

With asynchronous hooks, NodeJS provides an option to track asynchronous resources. To abstract the implementation, we use the NPM package async-local-storage, which is a simple package. Unfortunately, it has not been transferred to typescript yet. Since asynchronous hooks have an impact on NodeJS' performance, they are disabled by default. Therefore we have to enable them first in the application bootstrap method. Async-local-storage' interface provides the enable() function for that.

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { enable } from "async-local-storage";

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  
  // async hooks are disabled by default. Therefore turning them on:
  enable();

  await app.listen(3000);
}
bootstrap();

Next, we’ll add the hooks to our middleware. Each execution will then write the tenant into the asynchronous resource using the set(key, value) function from async-local-storage.

import { Injectable, NestMiddleware } from "@nestjs/common";
import { set } from "async-local-storage";

@Injectable()
export class TenantMiddleware implements NestMiddleware {

  use(req: any, res: any, next: () => void): any {
    let tenantName: string;
    tenantName = req.headers.host.split(".")[0];
    set("tenant", tenantName);

    next();
  }
}

And, you’re probably guessing it already, we can access the resource using get(key). This is how the resource can be accessed everywhere in the application now. Thus, let’s do that in the TenantService where all the Tenant logic is located.

  async getTenantFromNamespace(): Promise<Tenant> {
    const tenant: string = await get("tenant");
    return this.getTenantByName(tenant);
  }

And that’s it. This is how Tenants can be extracted using NestJS asynchronous hooks rather than provider factories. You can find the whole project here at Github :).