sd

Getting Started with a Multi-tenant Application on Node.js


Introduction to Multi-tenant application

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.

The following image shows the two architecture for separating data. Both strategies that can be used to design your software and comes with their unique set of nuances.

multi tenant database

Multi-tenancy is a key concept in building and delivering software-as-a-service (SaaS)solutions.

In this blog, I’ll share a concept of multitenant architecture and guide you through an approach to build a multitenant app with examples and sample code. We will be using NodeJS and ExpressJS along with PostgreSQL as the database.

Why Multitenancy?

  • Allows segregation of data across the clients.
  • Manage and customize the app according to the client’s needs and feasibility without actually altering the source code. A simple example would be providing separate branding for each tenant or using feature flags to provide different features for each tenant.
  • We can maximize efficiency while reducing the cost needed for centralized updates and maintenance. Maintenance for a multitenant application is easier as the updates applied to the system affects all the tenants and the upgrades and maintenance are usually handled by the SaaS company and not the individual customers.

Methods of Tenancy

When designing a multi-tenant SaaS application, you must carefully choose the tenancy design that best fits the needs of your application. A tenancy design determines the approach in managing, accessing, and separating tenant data.

I’d like to share two basic models that are commonly used when partitioning tenant data in a SaaS environment.

  1. 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. This blog explains more about how we can implement a logical separation of data.
  2. 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.

Getting Started with Multi-tenant application

Let’s dive into the implementation of the multi-tenant system. We will create an app using NodeJS and ExpressJS that will identify the tenant from each request and provide the data for that particular tenant database.

multi tenant database per tenant

Let start with a few definitions:

  • Tenant: A group of users with the specific privilege to their data.
  • Connection Resolver: An utility that identifies a tenant and resolves a database connection pool for that particular tenant.
  • Common Connection: An object which holds the information about the connection to a common database.
  • Tenant Connection: An object which holds the information about the connection to tenant database.
  • Common DB: Database which holds the information about all the tenant databases. It also stores the configuration for each tenant databases which can be used to enable/disable features for each tenant.
  • Tenant DB: Separate database for every tenant that holds data as per tenant needs.

Prerequisites

Installing Required Packages

First, let’s add packages which we require for setting up the application. Go to your project root and initialize a new project using the command yarn init or npm init. Then run the following scripts:x

yarn add dotenv body-parser knex pg # npm install dotenv body-parser knex pg --save

We are using knex as a query builder and pg as the database library as we are connecting to PostgreSQL Server Database. You can use any database and its library for your purpose.

Modernizing the Script Using Babel

We all want to modernize our code and to do so we need to add and configure Babel for the project.

  1. Add the required babel packages as the dev dependencies
yarn add @babel/cli @babel/core @babel/node @babel/preset-env — dev

2. Create a file called .babelrc and keep it on your project root directory. Add the following snippet to this file to enable transforms for ES2015+

{
  "presets": ["@babel/preset-env"]
}

3. Add the following start script in package.json

...
"scripts": {
  "start": "nodemon --watch src --exec babel-node src"
}
...

Booting up the App

Now let’s create a file index.js inside the src folder. We will initialize the express application and create a basic route.

Run the application using the command yarn start.

Database Configuration

Metadata of the tenant database

Now we need to create the databases: common_db, tenant1_db and tenant2_db.

Make sure you create different users and credentials for these databases.

Let’s create a table in common_db which will hold connection information for the tenant databases.

Connection Configuration for Common Database

We will now create an instance of knex and provide connection configuration of the common database. Make sure you have created a .env file in your project root.

Create a file env.js inside src and add the following snippet.

import dotenv from 'dotenv';
dotenv.config();

Add the following snippet in a file commonDBConnection.js.

Adding a Special Package

We need to connect to the correct tenant database based on which tenant has requested. One way of doing this is to pass the information about the tenant (that is received from the request) to every underlying service. This approach is not clean and also introduces redundancy.

Hence we will use a context that will persist across the async task. Let’s use a package called continuation-local-storage.

This package works like thread-local storage in threaded programming but is based on chains of Node-style callbacks instead of threads.

yarn add continuation-local-storage

Adding a Service to Manage Connections

Let’s create util called connectionManager.js that will help us connect to our tenant databases and resolve the connection.

First, let’s add functions to connect to all the tenant databases.

Now we need to add functions to this file that will return a connection from the connectionMap we created earlier.

We have now created a very sophisticated connection manager. To understand how we were able to use nameSpace.get('connection'), let’s see how we set it.

Creating Connection Resolver

Create a middleware to resolve the connection called connectionResolver.js inside the directory src/middlewares. This would be our middleware that figures out which connection to use throughout the request.

Adding Data for Tenants

Let’s add some data to the tenants. In this example, we will do this writing a custom query. We will create a table called users and add some data to it. You can do this by adding migrations to the application and running the migration to the tenant databases.

Create a Service to Get User Information

We can now query from these databases. Let’s make use of the connectionResolver.js and connectionManager.js we created before.

1. Create a service users.js inside src/services

As shown in the above code, we now don’t need to let the services know what connection we are using. Now, this is the kind of abstraction we love in code. You can further abstract it by creating your own models and refactoring the connection manager.

2. Add the following code to index.js

All Done

We are now at the end. We can start our application by using the command yarn start. Let’s perform some API calls in the following routes:

  • localhost:8080/users?slug=tenant1
  • localhost:8080/users?slug=tenant2

This should now respond the right users for the given tenant.

Here is the complete code for this implementation → Multitenant Application Example!

Conclusion

We hope this post has helped you understand one of the architectures used in multi-tenant applications. This implementation will help you get started with an application which you can scale in the future. However, this post doesn’t cover the techniques of securing your API requests or how we can implement such APIs in the frontend.

Go ahead, try these things for yourself and let us know if you liked this guide and want to know more about multi-tenant systems.


Stay informed on the latest news on AI, product management and building digital products. Subscribe to our newsletter below!

More in Insights

8 DevOps Tools and Services We Love Technology

8 DevOps Tools and Services We Love

The core focus of any DevOps team is evolving and improving products rapidly. At Leapfrog, we value the speed at

Read more
How We Moved Towards Serverless Architecture Technology

How We Moved Towards Serverless Architecture

The scalability, flexibility and reduced cost promised by serverless architecture resulted in a massive growth rate of 75% compared to

Read more
Enhancing Git Workflow with Smart Commit Open Source Tool Technology

Enhancing Git Workflow with Smart Commit Open Source Tool

When you have a predefined process, it is generally a good idea to automate the things that can be and

Read more