npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

loopback4-multi-tenancy

v0.3.0

Published

A LoopBack 4 extension for dynamic runtime connection and management of multitenant datasources.

Downloads

43

Readme

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.