0.3.0 • Published 10 months ago

loopback4-multi-tenancy v0.3.0

Weekly downloads
-
License
MIT
Repository
github
Last release
10 months ago

loopback4-multi-tenancy

LoopBack

Overview

This package provides a robust multitenancy solution for Loopback 4 applications, allowing you to efficiently manage multiple tenants within a single application instance. It is designed to be flexible, easy to integrate, and scalable to handle various multitenant architectures.

Features

  • Tenant Isolation: Ensure data isolation between tenants.
  • Dynamic Tenant Resolution: Automatically resolve tenants based on the incoming request.
  • Customizable Middleware: Easily extend and customize the package for your specific needs.
  • Support for Multiple Databases: Manage tenant-specific databases or schemas.
  • Flexible Tenant Detection Strategies: Choose from a variety of strategies to detect tenants, allowing you to tailor the multitenancy implementation to your architecture:
    • Header-Based Detection: Extract tenant information directly from the request headers, such as x-tenant-id.
    • JWT-Based Detection: Decode tenant information from JSON Web Tokens (JWTs) passed in the Authorization header
    • Host-Based Detection: Identify tenants based on the request's host or subdomain, useful for scenarios with tenant-specific domains.
    • Query Parameter Detection: Determine tenants using query parameters in the request URL, such as tenant-id.

Installation

Install MultiTenancyComponent using npm;

npm install loopback4-multi-tenancy

Basic Usage

To integrate loopback4-multi-tenancy into your LoopBack 4 application, follow these steps:

1. Import and Add Component to Application

Import and add the MultiTenancyComponent to your application:

import { MultiTenancyComponent } from 'loopback4-multi-tenancy';

// ...

export class MyApplication extends BootMixin(ServiceMixin(RepositoryMixin(RestApplication))) {
  constructor(options: ApplicationConfig = {}) {
    super(options);
    // ...
    this.component(MultiTenancyComponent);
    // ...
  }
}

2. Configure Tenant Detection Strategies

Set up the strategies to detect tenants. Four strategies are available — header, jwt, query, host. The default strategy is header, but you can specify others:

import { MultiTenancyBindings, MultiTenancyOptions } from 'loopback4-multi-tenancy';

// ...
this
  .configure<MultiTenancyOptions>(MultiTenancyBindings.ACTION)
  .to({ strategyNames: ['jwt', 'header', 'query'] });
// ...

3. Register MultiTenancy Action

To enforce multitenancy before other actions, add MultiTenancyAction to your sequence (src/sequence.ts):

import { MultiTenancyBindings, SetupMultitenancyFn } from 'loopback4-multi-tenancy';
import { inject } from '@loopback/core';
import { RequestContext, SequenceHandler } from '@loopback/rest';

export class MySequence implements SequenceHandler {
  constructor(
    @inject(MultiTenancyBindings.ACTION)
    private readonly setupMultitenancy: SetupMultitenancyFn,
  ) {}

  async handle(context: RequestContext) {
    // ...
    await this.setupMultitenancy();
    // ...
  }
}

4. Access the Current Tenant

To access the current tenant in your controllers, inject it as follows: Note: You should keep it at as last argument(cause its a optional argument)

import { inject } from '@loopback/core';
import { MultiTenancyBindings, Tenant } from 'loopback4-multi-tenancy';
import { post, requestBody, getModelSchemaRef } from '@loopback/rest';
import { User, UserRepository } from '../repositories';

export class UserController {
  constructor(
    @repository(UserRepository)
    public userRepository: UserRepository,
    @inject(MultiTenancyBindings.CURRENT_TENANT, { optional: true })
    private tenant?: Tenant,
  ) {}

  @post('/users', {
    responses: {
      '200': {
        description: 'User model instance',
        content: { 'application/json': { schema: getModelSchemaRef(User) } },
      },
    },
  })
  async create(
    @requestBody({
      content: {
        'application/json': {
          schema: getModelSchemaRef(User, { title: 'NewUser', exclude: ['id'] }),
        },
      },
    })
    user: Omit<User, 'id'>,
  ): Promise<User> {
    user.tenantId = this.tenant?.id ?? '';
    return this.userRepository.create(user);
  }
}

🚀 Advanced Usage

For dynamically configuring datasources at runtime, follow these steps:

1. Write a Datasource Config Provider

To connect datasources dynamically at runtime, create a datasource provider:

import { Provider } from '@loopback/core';
import { DatasourceConfigFn, Tenant } from 'loopback4-multi-tenancy';
import { TenantRepository } from './repositories';

export class MultitenancyDatasourceConfigProvider implements Provider<DatasourceConfigFn> {
  constructor(
    @repository(TenantRepository)
    private tenantRepo: TenantRepository,
  ) {}

  value(): DatasourceConfigFn {
    return async (currentTenant?: Tenant) => {
      if (currentTenant?.id) {
        const tenantData = await this.tenantRepo.findById(currentTenant.id);
        return tenantData.dbConfig;
      }
      return null;
    };
  }
}

2. 🚨 IMPORTANT Create a Default Datasource

Create a default datasource for the tenant. By default, this datasource will be connected to the application and all the repositories of the tenant. This will be replaced at runtime. Assuming you are creating a new datasource with the name tenant, this will be used in the next step

3. Configure Datasource Bind Key

Provide the exact bind key that is used in the repository and the datasource name of the default tenant datasource. The default is tenant:

import { MultiTenancyBindings, DatasourceOptions } from 'loopback4-multi-tenancy';

// ...
this.configure<DatasourceOptions>(MultiTenancyBindings.DATASOURCE_ACTION).to({datasourceBindKey: 'tenant'});
// ...

This key is custom and it can be used as per your choice but your repository must use specified key in injection.

export class UserRepository extends DefaultCrudRepository<User,
    typeof User.prototype.id,
    UserRelations> {
    constructor(
        @inject('datasources.tenant') dataSource: JugglerDataSource,
    ) {
        super(User, dataSource);
    }
}

4. Bind the Provider in application.ts

Bind the MultitenancyDatasourceConfigProvider to your application configuration:

import { MultiTenancyBindings } from 'loopback4-multi-tenancy';

this.bind(MultiTenancyBindings.DATASOURCE_CONFIG).toProvider(MultitenancyDatasourceConfigProvider);

5. Add Datasource Action Provider to the Sequence

If using an action-based sequence (not required for middleware-based sequences), add the datasource action provider to src/sequence.ts.

  • 🚨 Important: Configuration Order

    Ensure that the datasource configuration is executed after the multitenancy setup to avoid any issues with tenant context.

  • Steps:

  1. Complete the multitenancy setup
  2. Then configure the datasource
import { inject } from '@loopback/core';
import { SequenceHandler, RequestContext, SequenceActions } from '@loopback/rest';
import { MultiTenancyBindings, SetupMultitenancyFn, SetupDatasourceFn } from 'loopback4-multi-tenancy';

export class MySequence implements SequenceHandler {
  constructor(
    @inject(MultiTenancyBindings.ACTION)
    private readonly setupMultitenancy: SetupMultitenancyFn,
    @inject(MultiTenancyBindings.DATASOURCE_ACTION)
    private readonly setupDatasource: SetupDatasourceFn,
  ) {}

  async handle(context: RequestContext) {
    try {
      // ...
      await this.setupMultitenancy();
      await this.setupDatasource();
      // ...handle other sequence actions
    } catch (err) {
      this.reject(context, err);
    }
  }
}

That's all.

License

This project is licensed under the MIT License. See the LICENSE file for details.

Feedback

If you've noticed a bug or have a question or have a feature request, search the issue tracker to see if someone else in the community has already created a ticket. If not, go ahead and make one! All feature requests are welcome. Implementation time may vary. Feel free to contribute the same, if you can. If you think this extension is useful, please star it. Appreciation really helps in keeping this project alive.

0.1.0

10 months ago

0.3.0

10 months ago

0.2.0

10 months ago

0.0.9

11 months ago

0.0.8

11 months ago

0.0.6

11 months ago

0.0.4

11 months ago

0.0.3

11 months ago

0.0.2

11 months ago

0.0.1

11 months ago