# Multitenancy

Multi-tenancy is a software architecture where a single instance of software runs on a server and serves multiple tenants. A good example would be GitHub where each user or organization has their separate work area. This concept is used while developing software that runs for different organizations.

Multi-tenancy is a key concept in building and delivering software-as-a-service (SaaS)solutions. The following image shows the two architecture for separating data.

Markdown Monster icon

NOTE: Multitenancy is only available on ExpressWebjs SQL database at the moment

# Methods of multi-tenancy

We will like to share two basic models that are commonly used when partitioning tenant data in a SaaS environment.

  • Logical Separation of Data: In this approach, there is only one database for all tenants. Their data is separated by using some unique identifier for each client. The codebase is responsible for storing and retrieving data using this unique identifier.

  • Physical Separation of Data: This approach separates the data by provisioning different database for different tenants/clients. This helps us to scale our application as the number of clients grows and also scale the database as per the clients need. In this guide, we’ll be talking about multitenant architecture with physical separation of data.

Let’s dive into the implementation of the multi-tenant system. First we need to adjust our .env file by adding the following:

DB_MULTITENANCE = true;
DB_TENANT = null; //Current tenant for migration and seed

Next we will add our multitenancy configuration settings in Config/database.ts file.

 /*
  |--------------------------------------------------------------------------
  | Database Multitenance
  |--------------------------------------------------------------------------
  |
  | Database multitenace can be activated by switching the value to true and can
  | be deactivated by switching it to false.
  |
  */
  database_multitenance: env("DB_MULTITENANCE", false),
 /*
  |--------------------------------------------------------------------------
  | Multitenance Connections
  |--------------------------------------------------------------------------
  |
  | Database multitenace connection enables interaction with multiple
  | SQL databases where each database is a tenant in your system.
  | The tenant array accepts an object of database connections (tenants).
  |
  */
  multitenant_tenants: [
    {
      client: "mysql",
      connection: {
        host: env("DB_HOST"),
        port: env("DB_PORT"),
        user: env("DB_USER"),
        password: env("DB_PASSWORD"),
        database: "tenant1",
      },
      migrations: {
        directory: path.join(__dirname, "../Database/Migrations/tenant1/"),
        tableName: "migrations",
        stub: path.join(__dirname, "../Database/Migrations/migrationLayout.stub"),
        extension: "ts",
      },
      seeds: {
        directory: path.join(__dirname, "../Database/Seeds/tenant1/"),
      },
    },
    {
      client: "mysql",
      connection: {
        host: env("DB_HOST"),
        port: env("DB_PORT"),
        user: env("DB_USER"),
        password: env("DB_PASSWORD"),
        database: "tenant2",
      },
      migrations: {
        directory: path.join(__dirname, "../Database/Migrations/tenant2/"),
        tableName: "migrations",
        stub: path.join(__dirname, "../Database/Migrations/migrationLayout.stub"),
        extension: "ts",
      },
      seeds: {
        directory: path.join(__dirname, "../Database/Seeds/tenant2/"),
      },
    },
  ],

The objects in the multitenant_tenants section are the different databases(tenants) we are adding to our system. Remember it should all be SQL databases. Support for Nosql databases will be added shortly.

Once that is done, we can now create our tenant folders in Database/Migrations/ directory.

Database/Migrations/tenant1

Database/Migrations/tenant2

The folders will house our tenant migrations.

One last step is now to adjust SchemaSetup file in the root directory like so:

import env from "expresswebcorets/lib/ENV";
import config from "./Config/database";

class migration {
  constructor() {
    if (env("DB_CONNECTION") != "mongoose") {
      switch (env("DB_TENANT")) {
        case "tenant1":
          return config.multitenant_tenants[0];
        case "tenant2":
          return config.multitenant_tenants[1];
        case null:
          return config[env("DB_CONNECTION")];
        default:
          throw Error("DB_TENANT is not specified in .env file");
      }
    }
  }
}
export default new migration();

# Tenant1

For tenant1, we need to set DB_TENANT = tenant1; in .env file and restart our app.

This will enable our SchemaSetup file to switch to tenant1 migration ready state which will enable us generate migrations for tenant1.

# Tenant2

For tenant2, we need to set DB_TENANT = tenant2; in .env file and restart our app.

This will enable our SchemaSetup file to switch to tenant2 migration ready state which will enable us generate migrations for tenant2.

# Quering data from database

To query data from our database, we need to import the Multitenant module like so:

import Multitenant from "Elucidate/Database/Multitenancy";

Next we get the tenant we want to use:

let tenant = Multitenant.getTenant("tenant1");

Once that is done, we can now query our database;

let tenant = Multitenant.getTenant("tenant1");
let users = await UserModel.query(tenant);

Using it with SQLPD_repository for UserRepository

let tenant = Multitenant.getTenant("tenant1");
let user = await new UserRepository(tenant).findBy("first_name", "alex");

tenant1 can be passed as part of the request body

let tenant = Multitenant.getTenant(req.body.tenant);
let user = await new UserRepository(tenant).findBy("first_name", "alex");

# Tenant Middleware

You can also handle the tenant retrieval via a middleware