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 🙏

© 2026 – Pkg Stats / Ryan Hefner

saas-vs

v1.0.4

Published

A perfect library for hosting your software for multiple tenants

Readme

SAAS-VS (Software as a service)

Software as a service is a multi-tenant software hosting model. Using it's functions you will be able to register multiple tenants for your product and able to create separate database and product related tables for each tenant.

It can be used in two ways:

1. Single produt multitenant approach:

If you have only single product and there are multiple tenants and you want to host your product for multiple tenants then you can use this package with isMultiProduct flag as false. Where you do not need to provide any unique product id for your product. This usecase is the default behaviour of package.

Architecture diagram for single product multitenant usecase:

architecture_diagram

Sequence diagram for single product multitenant usecase

sequence_diagram

2. Multiple produts multitenant approach:

If you have multiple products and there are multiple tenants and you want to host your products for multiple tenants then you can use this package by passing isMultiProduct flag as true . Where you need to provide a unique product id for each of your products while using the package functions.

Architecture diagram for multiple products multitenant usecase:

architecture_diagram

Sequence diagram for multiple products multitenant usecase

sequence_diagram


Installation

Using npm:

# Install package with npm

$  npm install saas-vs

Import module


import { MultiTenantController } from "saas-vs";
const multiTenantController = new MultiTenantController();

Requirements:

  • Create databse folder in root of your project and inside it schema folder and place file containing schema creation queries i.e .sql file for a product inside it.

For example create schema.sql inside database/schema folder path.

this can contain queries like this

   CREATE TABLE IF NOT EXISTS
        employee (
          autoUniqueId INT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
          name VARCHAR(255) NOT NULL,
          code VARCHAR(255) NOT NULL,
          firstName VARCHAR(255) NOT NULL,
          middleName VARCHAR(255),
          lastName VARCHAR(255) NOT NULL,
          company INT(11) NOT NULL,
        }ENGINE = InnoDB;
  • There should be one db.config.json file present in your root directory with below required keys. You can take reference from db.config.sample file as well present inside root of this package.

For single product usecase(i.e default behaviour with isMultiProduct flag as false) db.config.json file will look like:

 {
    "MTS_MASTER_DB_PORT": 3306,
    "MTS_MASTER_DB_HOSTNAME": "YOUR_DB_HOSTNAME",
    "MTS_MASTER_DB_NAME": "YOUR_DB_NAME",
    "MTS_MASTER_DB_USERNAME": "YOUR_DB_USERNAME",
    "MTS_MASTER_DB_PASSWORD": "YOUR_DB_PASSWORD",
    "MTS_PRODUCT_DB_SCHEMA_PATH": "PRODUCT_SCHEMA_PATH",
}

This will include your master db details in which all registered organisations information is stored. There will be one more key MTS_PRODUCT_DB_SCHEMA_PATH which will be path of schema file stored in database/schema folder in your project for a registered product. All the queries listed in this file will get execute during post call.

For example for your product suppose schema file name is schema.sql(as specified in point one above) , then in db.config.json MTS_PRODUCT_DB_SCHEMA_PATH key will look like this

"MTS_PRODUCT_DB_SCHEMA_PATH" :  "schema.sql"

For multiple products usecase (i.e with isMultiProduct flag as true) db.config.json file will look like:

 {
    "MTS_MASTER_DB_PORT": 3306,
    "MTS_MASTER_DB_HOSTNAME": "YOUR_DB_HOSTNAME",
    "MTS_MASTER_DB_NAME": "YOUR_DB_NAME",
    "MTS_MASTER_DB_USERNAME": "YOUR_DB_USERNAME",
    "MTS_MASTER_DB_PASSWORD": "YOUR_DB_PASSWORD",
    "MTS_PRODUCT_CONFIG": { 
        "PRODUCT_1_ID": {
            "db_schema_path": "PRODUCT_1_SCHEMA_PATH"
        },
        "PRODUCT_2_ID": {
            "db_schema_path": "PRODUCT_2_SCHEMA_PATH"
        }
    }
}

This will include your master db details in which all registered organisation information is stored. There will be one more key MTS_PRODUCT_CONFIG which will be an object containing db schema path for requested products. You can specify schema path for multiple products in it.

db_schema_path : It will be path of schema file stored in database/schema folder in your project for a registered product. All the queries listed in this file will get execute during post call.

For example for product with id 41e27793-4d90-11ef-8910-0a9df3751e43 , suppose schema file name is schema.sql(as specified in point one above) , then in db.config.json MTS_PRODUCT_CONFIG key will look like this

"MTS_PRODUCT_CONFIG" : {"41e27793-4d90-11ef-8910-0a9df3751e43": {"db_schema_path": "schema.sql"}}

1. post : To register a particular organisation for a product

post(orgInformation,isMultiProduct) function creates database , schema for registered product and also creates customers table inside master db if not exists and inserts record in it for registered organisation with newly created db details. In response it will return newly created orgId along with all the details for registered product.

post(orgInformation: IOrgInformation, isMultiProduct:boolean =false): Promise<ICustomer>;

Usage if single product multitenant approach:

customers table with below schema will be created automatically if not exists in your master db. It will contain details of all registered organisations.

CREATE TABLE `customers` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `org_id` varchar(40) NOT NULL DEFAULT (uuid()),
  `org_name` varchar(255) NOT NULL,
  `domain` varchar(255) NOT NULL,
  `first_name` varchar(255) NOT NULL,
  `last_name` varchar(255) DEFAULT NULL,
  `contact_number` varchar(50) NOT NULL,
  `contact_email` varchar(255) NOT NULL,
  `phone_country_code` varchar(20) DEFAULT NULL,
  `website` varchar(255) DEFAULT NULL,
  `license` text,
  `license_expiry` date DEFAULT NULL,
  `address` text,
  `db_credentials` json DEFAULT NULL,
  `env_vars` json DEFAULT NULL,
  `status` enum('ACTIVE','INACTIVE') NOT NULL,
  `created_at` datetime NOT NULL DEFAULT (utc_timestamp()),
  `updated_at` datetime NOT NULL DEFAULT (utc_timestamp()),
  PRIMARY KEY (`id`),
  UNIQUE KEY `unique_domain__customers` (`domain`),
  UNIQUE KEY `unique_org_id__customers` (`org_id`),
  INDEX `domain_and_status_idx` (`domain`,`status`)
) ENGINE=InnoDB;
    const orgInformation = {
        first_name: "abc",
        email: "[email protected]",
        org_name: "org pvt. ltd.",
        contact_number: "9999999999",
        last_name: "abc",   //optional
        phone_country_code: "+91", //optional
        website: "abc.com", //optional
        license: "abc", //optional
        license_expiry: "2024-11-1", //optional
        address: "abc", //optional
        status: "ACTIVE" //optional
        env_vars: {var1:'abc'} //optional
    }
    const response = await multiTenantController.post(orgInformation)
    or 
    const response = await multiTenantController.post(orgInformation, false)
    // response will look like
    {
    "org_id": "e3afd9ae-177c-4809-b39e-39c1167bc7c3",
    "id": 1,
    "first_name": "abc",
    "last_name": "abc",
    "contact_number": "9999999999",
    "contact_email": "[email protected]",
    "org_name": "org pvt. ltd.",
    "phone_country_code": "+91",
    "domain": "org.com",
    "status": "ACTIVE",
    "website": "org.com",
    "license": "abc",
    "license_expiry": "2024-10-31T18:30:00.000Z",
    "address": "abc",
    "env_vars": {var1:'abc'},
    "db_credentials": {
        "RDS_PORT": 3306,
        "RDS_DATABASE": "org",
        "RDS_HOSTNAME": "abc",
        "RDS_PASSWORD": "abc",
        "RDS_USERNAME": "abc"
    }
  }
    

Usage if multiple products multitenant approach:

customers table with below schema will be created automatically if not exists in your master db. It will contain details of all registered organisations.

   CREATE TABLE `customers` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `org_id` varchar(40) NOT NULL DEFAULT (uuid()),
  `org_name` varchar(255) NOT NULL,
  `domain` varchar(255) NOT NULL,
  `product_id` varchar(40) NOT NULL,
  `first_name` varchar(255) NOT NULL,
  `last_name` varchar(255) DEFAULT NULL,
  `contact_number` varchar(50) NOT NULL,
  `contact_email` varchar(255) NOT NULL,
  `phone_country_code` varchar(20) DEFAULT NULL,
  `website` varchar(255) DEFAULT NULL,
  `license` text,
  `license_expiry` date DEFAULT NULL,
  `address` text,
  `db_credentials` json DEFAULT NULL,
  `env_vars` json DEFAULT NULL,
  `status` enum('ACTIVE','INACTIVE') NOT NULL,
  `created_at` datetime NOT NULL DEFAULT (utc_timestamp()),
  `updated_at` datetime NOT NULL DEFAULT (utc_timestamp()),
  PRIMARY KEY (`id`),
  UNIQUE KEY `unique_domain_and_product_id__customers` (`domain`,`product_id`),
  UNIQUE KEY `unique_org_id_and_product_id__customers` (`org_id`,`product_id`),
  INDEX `domain_product_id_and_status_idx` (`domain`,`product_id`,`status`)
) ENGINE=InnoDB;

If domain already exists then it will create new entry with same orgId for new product. product_id is required in this case.

    const orgInformation = {
        product_id: "41e27793-4d90-11ef-8910-0a9df3751e43",
        first_name: "abc",
        email: "[email protected]",
        org_name: "org pvt. ltd.",
        contact_number: "9999999999",
        last_name: "abc",   //optional
        phone_country_code: "+91", //optional
        website: "abc.com", //optional
        license: "abc", //optional
        license_expiry: "2024-11-1", //optional
        address: "abc", //optional
        status: "ACTIVE" //optional
        env_vars: {var1:'abc'} //optional
    }
    const response = await multiTenantController.post(orgInformation, true)
    
    // response will look like
    {
    "org_id": "e3afd9ae-177c-4809-b39e-39c1167bc7c3",
    "id": 1,
    "first_name": "abc",
    "last_name": "abc",
    "contact_number": "9999999999",
    "contact_email": "[email protected]",
    "org_name": "org pvt. ltd.",
    "phone_country_code": "+91",
    "domain": "org.com",
    "product_id": "41e27793-4d90-11ef-8910-0a9df3751e43",
    "status": "ACTIVE",
    "website": "org.com",
    "license": "abc",
    "license_expiry": "2024-10-31T18:30:00.000Z",
    "address": "abc",
    "env_vars": {var1:'abc'},
    "db_credentials": {
        "RDS_PORT": 3306,
        "RDS_DATABASE": "org",
        "RDS_HOSTNAME": "abc",
        "RDS_PASSWORD": "abc",
        "RDS_USERNAME": "abc"
    }
  }
    

2. get : To get the registered organisation details for a product

get(domainName,isMultiProduct,productId) function fetches the registered organisation details. If no registered organisation is found then it will return null.

get(domainName: string, isMultiProduct: boolean = false, productId?: string): Promise<ICustomer | null>;

Usage if single product multitenant approach:

   const domainName = "abc";
   
   const response = await multiTenantController.get(domainName);
   or
   const response = await multiTenantController.get(domainName, false);
   // response will be same as in case of post
    

Usage if multiple products multitenant approach:

productId is required in this case

   const domainName = "abc";
   const productId = "41e27793-4d90-11ef-8910-0a9df3751e43";
   
   const response = await multiTenantController.get(domainName, true, productId);
   
   // response will be same as in case of post
    

3. isValid : To check if domain has been registered for a product and it's status is currently active or not

isValid(domainName,isMultiProduct,productId) function finds the active org on the basis of domain name and product id (if it's a multiple producs multitenant use case) from customers table. If it exists then it will return true otherwise false.

isValid(domainName: string, isMultiProduct: boolean = false, productId?: string): Promise<Boolean>;

Usage if single product multitenant approach:

   const domainName = "abc";
   
   const response = await multiTenantController.isValid(domainName);
   or 
   const response = await multiTenantController.isValid(domainName, false);
   // response will be true or false
    

Usage if multiple products multitenant approach:

productId is required in this case

   const domainName = "abc";
   const productId = "41e27793-4d90-11ef-8910-0a9df3751e43";
   
   const response = await multiTenantController.isValid(domainName, true, productId);
   
   // response will be true or false
    

4. getConnectionDetails : To get db connecton details for a registered product

getConnectionDetails(domainName,isMultiProduct,productId) function returns the db details on the basis of domain name and product id(if it's a multiple producs multitenant use case) from customers table.

getConnectionDetails(domainName: string, isMultiProduct: boolean = false, productId?: string): Promise<IDbCredentials | null>;

Usage if single product multitenant approach:

   const domainName = "abc";
   
   const response = await multiTenantController.getConnectionDetails(domainName);
   or
   const response = await multiTenantController.getConnectionDetails(domainName, false);
   // response will look like
   
   {
    "RDS_PORT": 3306,
    "RDS_DATABASE": "abc",
    "RDS_HOSTNAME": "abc",
    "RDS_PASSWORD": "abc",
    "RDS_USERNAME": "abc"
 }
    

Usage if multiple products multitenant approach:

productId is required in this case

   const domainName = "abc";
   const productId = "41e27793-4d90-11ef-8910-0a9df3751e43";
   
   const response = await multiTenantController.getConnectionDetails(domainName, true, productId);
   
   // response will look like
   
   {
    "RDS_PORT": 3306,
    "RDS_DATABASE": "abc",
    "RDS_HOSTNAME": "abc",
    "RDS_PASSWORD": "abc",
    "RDS_USERNAME": "abc"
 }
    

5. put : To update registered organisation information

put(orgInformation, isMultiProduct) function updates the information of registered organisation of a product in customers table

put(orgInformation: IOrgUpdateInformation, isMultiProduct: boolean = false): Promise<void>;

Usage if single product multitenant approach:

    const orgInformation = {
        domain_name: "abc",
        first_name: "abc", //optional
        org_name: "org pvt. ltd.", //optional
        contact_number: "9999999999", //optional
        last_name: "abc",   //optional
        phone_country_code: "+91", //optional
        website: "abc.com", //optional
        license: "abc", //optional
        license_expiry: "2024-11-1", //optional
        address: "abc", //optional
        status: "ACTIVE", //optional
        env_vars: {var1:'abc'}, //optional
        db_credentials: {RDS_PORT: 123, RDS_DATABASE: 'abc',RDS_HOSTNAME: 'abc', RDS_PASSWORD: 'abc',RDS_USERNAME: 'abc'} //optional
    };
    
    await multiTenantController.put(orgInformation)
    or
    await multiTenantController.put(orgInformation, false)
    

Usage if multiple products multitenant approach:

product_id is required in this case

    const orgInformation = {
        product_id: "41e27793-4d90-11ef-8910-0a9df3751e43",
        domain_name: "abc",
        first_name: "abc", //optional
        org_name: "org pvt. ltd.", //optional
        contact_number: "9999999999", //optional
        last_name: "abc",   //optional
        phone_country_code: "+91", //optional
        website: "abc.com", //optional
        license: "abc", //optional
        license_expiry: "2024-11-1", //optional
        address: "abc", //optional
        status: "ACTIVE", //optional
        env_vars: {var1:'abc'}, //optional
        db_credentials: {RDS_PORT: 123, RDS_DATABASE: 'abc',RDS_HOSTNAME: 'abc', RDS_PASSWORD: 'abc',RDS_USERNAME: 'abc'} //optional
    };
    
    await multiTenantController.put(orgInformation, true)