{"id":19701206,"url":"https://github.com/alexanderc/nestjs-mtenant","last_synced_at":"2025-04-29T13:32:54.189Z","repository":{"id":57310155,"uuid":"288108741","full_name":"AlexanderC/nestjs-mtenant","owner":"AlexanderC","description":"NestJS - an module to enable multitenancy support with deep integration into the system as whole","archived":false,"fork":false,"pushed_at":"2023-07-10T13:07:29.000Z","size":978,"stargazers_count":107,"open_issues_count":1,"forks_count":10,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-04-05T17:36:09.722Z","etag":null,"topics":["multitenant","namespace","nest","nestjs","tenant","whitelabel"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/AlexanderC.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2020-08-17T07:09:53.000Z","updated_at":"2025-03-24T19:07:49.000Z","dependencies_parsed_at":"2024-03-22T10:49:58.438Z","dependency_job_id":null,"html_url":"https://github.com/AlexanderC/nestjs-mtenant","commit_stats":null,"previous_names":[],"tags_count":5,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AlexanderC%2Fnestjs-mtenant","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AlexanderC%2Fnestjs-mtenant/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AlexanderC%2Fnestjs-mtenant/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AlexanderC%2Fnestjs-mtenant/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/AlexanderC","download_url":"https://codeload.github.com/AlexanderC/nestjs-mtenant/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":251509802,"owners_count":21600707,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["multitenant","namespace","nest","nestjs","tenant","whitelabel"],"created_at":"2024-11-11T21:08:12.766Z","updated_at":"2025-04-29T13:32:53.866Z","avatar_url":"https://github.com/AlexanderC.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n  \u003ca href=\"http://nestjs.com/\" target=\"blank\"\u003e\n    \u003cimg src=\"https://nestjs.com/img/logo_text.svg\" width=\"320\" alt=\"Nest Logo\" /\u003e\n  \u003c/a\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  A module to enable multitenancy support with deep integration into the system as whole.\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://npmjs.com/package/nestjs-mtenant\"\u003e\u003cimg src=\"https://img.shields.io/npm/v/nestjs-mtenant.svg\" alt=\"NPM Version\" /\u003e\u003c/a\u003e\n  \u003ca href=\"https://npmjs.com/package/nestjs-mtenant\"\u003e\u003cimg src=\"https://img.shields.io/npm/l/nestjs-mtenant.svg\" alt=\"Package License\" /\u003e\u003c/a\u003e\n  \u003ca href=\"https://npmjs.com/package/nestjs-mtenant\"\u003e\u003cimg src=\"https://img.shields.io/npm/dm/nestjs-mtenant.svg\" alt=\"NPM Downloads\" /\u003e\u003c/a\u003e\n\u003c/p\u003e\n\n### Rationale\n\n*Warning: This module is based on `async_hooks`, which is still an experimental feature. Use it on your own risk!*\n\nMultitenancy is widely used acros the web as software deployment options called **whitelabels**. Data in between tenants are separated,\nhowever nowadays there is business by sharing data inbetween the peer businesses; as an example might serve a E-commerce platform that shares\ntheir clients with twin/friendly shop, or there's some unified backoffice interface... Thus a good idea would be\n**having the data under the same database (think namespace)** instead of having to separate into different databases,\nin order to be able to query it efficiently and avoid duplication or synchronization issues.\n\n### Installation\n\n```sh\nnpm install --save nestjs-mtenant sequelize-typescript\n#or\nyarn add nestjs-mtenant sequelize-typescript\n```\n\n\u003e Important: `sequelize-typescript` is required if you are using `sequelize` policy storage model.\n\n### Configuration\n\nConfigure your models:\n```typescript\n// models/user.model.ts\nimport { MTEntity } from 'nestjs-mtenant';\n\n// @MTEntity({ tenantField?: 'tenant', idField?: 'id' })\n@MTEntity() \n@Table({})\nexport default class User extends Model\u003cUser\u003e {\n  id: string; // idField\n  tenant: string; // tenantField\n  username: string;\n}\n\n// models/book.model.ts\nimport { MTEntity } from 'nestjs-mtenant';\n\n// @MTEntity({ tenantField?: 'tenant', idField?: 'id' })\n@MTEntity() \n@Table({})\nexport default class Book extends Model\u003cBook\u003e {\n  id: string; // idField\n  tenant: string; // tenantField\n  title: string;\n  content: string;\n}\n```\n\n\u003e Tenant Entity is enhanced with the following properties:\n\u003e ```typescript\n\u003e export interface TenantEntity {\n\u003e   getTenant(): string; // e.g. \"root\"\n\u003e   getTenantId(): string; // e.g. \"root/33\"\n\u003e }\n\u003e ```\n\n*(OPTIONAL)* Configure tenant storage using sequelize adapter:\n```typescript\n// models/tenants-storage.model.ts\nimport { TenantsStorageSequelizeModel } from 'nestjs-mtenant';\n\nexport default class TenantsStorage extends TenantsStorageSequelizeModel\u003cTenantsStorage\u003e {  }\n```\n\nAnd finaly include the module and the service *(assume using [Nestjs Configuration](https://docs.nestjs.com/techniques/configuration))*:\n```typescript\n// src/app.module.ts\nimport { MTModule, MTModuleOptions, SEQUELIZE_STORAGE, IOREDIS_CACHE } from 'nestjs-mtenant';\nimport { TenantSettingsDto } from './tenancy/tenant-settings.dto';\nimport TenantsStorage from '../models/tenants-storage.model';\nimport User from '../models/user.model';\nimport Book from '../models/book.model';\n\n@Module({\n  imports: [\n    MTModule.forRootAsync({\n      imports: [ConfigModule, RedisModule],\n      inject: [ConfigService, RedisService],\n      useFactory: async (configService: ConfigService, redisService: RedisService) =\u003e {\n        return {\n          for: [User, Book],\n          // The below options are optional\n          storage: SEQUELIZE_STORAGE,\n          storageSettingsDto: TenantSettingsDto,\n          storageRepository: TenantsStorage,\n          cache: IOREDIS_CACHE,\n          cacheClient: \u003cIORedis.Redis\u003eawait redisService.getClient(), \n          cacheOptions: { expire: 600 }, // tenant cache expires in 10 minutes (default 1 hour)\n        };\n      },\n    }),\n  ],\n},\n```\n\n\u003e Configuration reference:\n\u003e \n\u003e ```typescript\n\u003e export interface MTModuleOptions {\n\u003e  for: Array\u003cTenantEntity | Function\u003e, // Entities to handle, e.g. [BookModel, UserModel]\n\u003e  transport?: TenantTransport; // Tenant transport: http\n\u003e  headerName?: string; // Header name to extract tenant from (if transport=http specified)\n\u003e  queryParameterName?: string; // Query parameter name to extract tenant from (if transport=http specified)\n\u003e  defaultTenant?: string; // Tenant to assign by default\n\u003e  allowTenant?: (context: TenantContext, tenant: string) =\u003e boolean; // Allow certain requested tenant, augmented by tenant storage if setup\n\u003e  allowMissingTenant?: boolean; // Get both IS NULL and tenant scopes on querying\n\u003e  storage?: string | Storage; // dynamic tenant storage (e.g. sequelize)\n\u003e  storageSettingsDto?: any, // Tenant settings interface\n\u003e  storageRepository?: any; // if database storage specified\n\u003e  cache?: string | Cache; // if storage specified! dynamic tenant storage cache (e.g. ioredis)\n\u003e  cacheClient?: any; // if cache adapter specified\n\u003e  cacheOptions?: CachedStorageOptions; // if cache adapter specified\n\u003e }\n\u003e ```\n\n\u003e Important: if `storage` is setup- `allowTenant` is augmented by storage manager.\n\u003e Which would mean that the manager checks if tenant exists in the storage itself and pass it to next to `allowTenant` guard\n\u003e (otherwise returning false, `allowTenant` not being called at all; *@ref `MTService.isTenantAllowed()`*).\n\n### Usage\n\nThere's literally nothing to configure, except a decorator \nto enrich Swagger docs by adding description of tenancy transport (e.g. through an `@ApiHeader()` and an `@ApiQuery()`)\nin your controllers that support multi-tenancy:\n\n```typescript\nimport { MTApi } from 'nestjs-mtenant';\n\n@MTApi()\n@Controller()\nexport class BooksController {\n  // If \"includeQuery=true\" parameter specified, it will allow\n  // setting tenant via MT_QUERY_PARAMETER_NAME query param.\n  @MTApi({ includeQuery: true })\n  someAction() {  }\n}\n```\n\n\u003e Tenancy scope taken from the transport specified will be injected into instances and queries.\n\u003e If `allowMissingTenant=true` specified- queries will select entries for both- the tenant and missing tenant.\n\nSwitch model tenancy globally:\n\n```typescript\nBookModel.switchTenancy(/* enabled =*/ false)\n```\n\nSwitch model tenancy for a single operation (*e.g. super-admin related ops*):\n\n```typescript\n// supports any operation supporting option parameter, incl. bulk ones\nBookModel.create({...}, { disableTenancy: true });\n// for complex operations with includes\nUserModel.findAll({\n  disableTenancy: true,\n  include: [ { model: BookModel } ],\n});\n// ... or disable only for BookModel\nUserModel.findAll({\n  include: [ { model: BookModel, disableTenancy: true } ],\n});\n```\n\nSetting custom tenant or disabling tenancy for the current request scope:\n\n```typescript\nimport { MTService } from 'nestjs-mtenant';\n\n@Injectable()\nexport class UsersService {\n  constructor(\n    @InjectModel(User) private userModel: typeof User,\n    private readonly tenancyService: MTService,\n  ) { }\n\n  // Will enforce using it for subsequent model operations within the scope\n  // e.g. (await BooksService.useCustomTenant('custom-one')).create(...)\n  // This might be set in controllers as well, when taking from a DTO when\n  // there's no option to use a header.\n  // Optionally you might set MT_QUERY_PARAMETER_NAME query paremeter to achieve the same...\n  async useCustomTenant(tenant: string) {\n    await this.tenancyService.setTenant(tenant);\n    return this;\n  }\n\n  // Disabling tenancy for the current request scope (e.g. controller)\n  // You might need to disable tenancy without changing subsequent services logic\n  async disableTenancy() {\n    this.tenancyService.disableTenancyForCurrentScope();\n    return this;\n  }\n}\n```\n\nTypical stored tenants manager service (*if `storage` option configured*):\n\n```typescript\n// src/tenancy/tenant-settings.dto.ts\nexport class TenantSettingsDto {\n  someSetting?: string,\n  otherOne?: any,\n}\n\n// src/tenancy/tenancy.service.ts\nimport { Injectable } from '@nestjs/common';\nimport { MTService, StoredTenantEntity } from 'nestjs-mtenant';\nimport { TenantSettingsDto } from './tenant-settings.dto';\n\n@Injectable()\nexport class TenancyService {\n  constructor(\n    private readonly tenancyService: MTService,\n  ) { }\n\n  async setting(name: keyof TenantSettingsDto, defaultValue: any): Promise\u003cany\u003e {\n    const { tenant } = this.tenancyService.tenancyScope;\n\n    if (tenant === this.tenancyService.defaultTenancyScope.tenant) {\n      return defaultValue;\n    }\n\n    const tenantEntity = await this.get(tenant);\n\n    if (!tenantEntity) {\n      return defaultValue;\n    }\n\n    return (tenantEntity as StoredTenantEntity\u003cTenantSettingsDto\u003e).settings[name] || defaultValue;\n  }\n\n  async add(tenant: string, settings: TenantSettingsDto):\n    Promise\u003cStoredTenantEntity\u003cTenantSettingsDto\u003e\u003e {\n    return this.tenancyService.storage.add(tenant, settings);\n  }\n\n  async remove(tenant: string): Promise\u003cnumber\u003e {\n    return this.tenancyService.storage.remove(tenant);\n  }\n\n  async exists(tenant: string): Promise\u003cBoolean\u003e {\n    return this.tenancyService.storage.exists(tenant);\n  }\n\n  async updateSettings(tenant: string, settings: TenantSettingsDto):\n    Promise\u003cStoredTenantEntity\u003cTenantSettingsDto\u003e\u003e {\n    return this.tenancyService.storage.updateSettings(tenant, settings);\n  }\n\n  async get(tenant?: string):\n    Promise\u003cStoredTenantEntity\u003cTenantSettingsDto\u003e | Array\u003cStoredTenantEntity\u003cTenantSettingsDto\u003e\u003e\u003e {\n    return this.tenancyService.storage.get(tenant);\n  }\n}\n```\n\n\u003e Storage interface reference (*where `T === typeof TenantSettingsDto`*):\n\u003e \n\u003e ```typescript\n\u003e export interface TenantEntity\u003cT\u003e {\n\u003e   tenant: string,\n\u003e   settings: T,\n\u003e }\n\u003e\n\u003e export interface Storage\u003cT\u003e {\n\u003e   add(tenant: string, settings?: T): Promise\u003cTenantEntity\u003cT\u003e\u003e;\n\u003e   remove(tenant: string): Promise\u003cnumber\u003e;\n\u003e   exists(tenant: string): Promise\u003cBoolean\u003e;\n\u003e   updateSettings(tenant: string, settings: T): Promise\u003cTenantEntity\u003cT\u003e\u003e;\n\u003e   get(tenant: string): Promise\u003cTenantEntity\u003cT\u003e\u003e;\n\u003e }\n\u003e ```\n\n\n`nestjs-mtenant` integrates pretty well with the [`nestjs-iacry`](https://github.com/AlexanderC/nestjs-iacry#readme) module:\n\n```typescript\n// models/user.model.ts\nimport { IACryEntity } from 'nestjs-iacry';\nimport { MTEntity, DEFAULT_TENANT } from 'nestjs-mtenant';\n\n@MTEntity() \n@IACryEntity({ nameField: 'principal' })\n@Table({})\nexport default class User extends Model\u003cUser\u003e {\n  id: string; // idField\n  tenant: string; // tenantField\n  role?: string;\n\n  // Get principals like \"root/admin:33\"\n  @Column(DataType.VIRTUAL)\n  get principal() {\n    return `${this.getDataValue('tenant') || DEFAULT_TENANT}/${this.getDataValue('role')}`;\n  }\n}\n```\n\nA typical example of `nestjs-iacry` policies would look like:\n\n```typescript\n// Allow everything for admins from any tenant\n[\n  {\n    Effect: Effect.ALLOW,\n    Action: '!tenancy', // allow anything but tenancy related stuff\n    Principal: `*/${UserRoles.Admin}`,\n  },\n  {\n    Effect: Effect.ALLOW,\n    Action: '*',\n    Principal: `${DEFAULT_TENANT}/${UserRoles.Admin}`,\n  },\n]\n```\n\n### Development\n\nRunning tests:\n```bash\nnpm test\n```\n\nReleasing:\n```bash\nnpm run format\nnpm run release # npm run patch|minor|major\nnpm run deploy\n```\n\n### TODO\n\n- [ ] Add support for TypeORM\n- [ ] Add support for strategies (e.g. different database, table suffix)\n- [ ] Cover most of codebase w/ tests\n- [ ] Add comprehensive Documentation\n\n### Contributing\n\n* [Alex Cucer](https://github.com/AlexanderC)\n\n### License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falexanderc%2Fnestjs-mtenant","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Falexanderc%2Fnestjs-mtenant","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falexanderc%2Fnestjs-mtenant/lists"}