R.GOLKHANDANI
Home / Blog / Posts / Multi-Tenancy in NestJS
online--:--:--

Reza Golkhandani // Senior Software Engineer | Backend platforms

Two NestJS Packages for Database-per-Tenant, and the Design Behind Them

NestJS | MongoDB | MySQL | Open source

01

Two NestJS Packages for Database-per-Tenant, and the Design Behind Them

The goal

As the first engineer at a startup building a restaurant management system, I needed tenancy that could not be forgotten. Row-level filtering depends on every developer remembering a clause on every query. The goal was isolation as a guarantee: a database per tenant, with the mechanics hidden so feature code never sees a tenant at all. The two packages that came out of it are on npm.

The approach

Resolve the tenant once per request in the framework layer, and hand every service a model that is already bound to the right database.

What I built

mongoose-tenancy-nest. A request-scoped provider reads the tenant identifier, switches the shared connection to that tenant's database using the driver's cached useDb, registers any dependent models the main model references, and returns a bound model. Services inject it by name.

static modelProvider(name, main, dependencies?) {
  return {
    scope: Scope.REQUEST,
    provide: name,
    useFactory: (tenancy, request) =>
      tenancy.getModelForTenancy(request.headers.tenant, main, dependencies),
    inject: [MongooseTenancy, 'REQUEST'],
  };
}

Two details that matter: the database handle is cached, so switching is a lookup rather than a connection, and dependent models register on the same handle before the main one so population across collections works.

mysql2-nestjs. A dynamic module wrapping a connection pool, with register and registerAsync so options come from configuration or a factory. The pool is created lazily on first use and probed with a trivial query, so a bad configuration fails loudly at startup rather than on the first customer request. One injection token exposes it everywhere.

The result

The forgotten-filter class of bug disappeared, because there was nothing to forget. Per-customer backup became "copy this database". Both packages have been in use since and are small enough to read in one sitting. Today I would keep request scope to the model provider alone, take the tenant from a verified token claim, and bound the pool cache; all three are on the roadmap.

Takeaways

  • Row-level tenancy is a convention. Database-per-tenant is a guarantee.
  • Hide tenancy in the framework layer so feature code cannot get it wrong.
  • Publish the small thing. Code people can read in a sitting gets better faster.
02

Connect with Me