Build a multi-tenant NodeJS app, with Sequelize and Cilo / Part 1

M. Khaled Badenjki
4 min readJun 16, 2023

--

Introduction

Multi tenancy is an architectural pattern that enables an application to handle multiple clients (or tenants) as if every client has their own app.

This concept is not new at all. From the 1990s, traditional application service providers (ASPs) hosted applications on behalf of their customers. Although some customers required their apps to be hosted on a separate physical machines, other customers didn’t mind sharing their apps with other customers, and only have their own processes (not their own physical machines) while paying less.

This evolved with time, but in principle, the same is happening now. When you rent an EC2 instance from AWS, you don’t rent a whole machine but most probably you only get part of the machine resources. Although the provided terminal make it look like its your own machine, you know it’s not a machine but only part of it.

With the rise of Software as a Service (SaaS), the need for multi tenancy increased. This is specially true when it comes to Business To Business apps (B2B). Businesses usually prefer their data to be separated from other businesses. The need for data separation increases with the sensetivity of the app. For example an accounting system has highly sesentive data, and therefore higher need for data separation.

Types of Multi tenancy

Silo, Pool and Bridge models.

In a nutshell, this is what each model provides:

  • Silo: fully independant tech stack, or separate database for each client. From the name you can tell that Cilo package aims to achieve the Silo type.
  • Pool: shared resources. For example, a shared database for all tenants, with a column holding the id of tenant in each row. This is the most popular scenario (it’s a fake multitenancy because everything is sharted).
  • Bridge: a mix of both.

For more detailed description, you can check this article.

Prerequisuite

Tools required to follow up:

  • Node installed on your machine (v18 or higher)
  • NPM or Yarn (I prefer yarn)
  • VSCode
  • Any database client (I use DBeaver community version)

Once they are ready, let’s start.

Implementation

Setup a basic app

A simple bookstore app will have the following functionality:

  • Registeration
  • Listing books
  • Create a book

Run this command to create a directory, initialize an app and open vscode:

mkdir bookstore && cd bookstore && yarn init && code .

Now that we have the app open, let’s install the needed packages

yarn add express sequelize cilo

and this command for development tools

yarn add -D sequelize-cli nodemon

Now let’s setup an express app and a /health endpoint to check the healthness of the app:

touch app.js

Inside the app.js file, let’s create a simple app and run it on port 3000

const express = require('express')
const app = express()
const port = 3000

app.get('/health', (req, res) => {
res.json({
health: 'healthy'
})
})

app.listen(port, () => {
console.log(`Cilo app listening on port ${port}`)
})

run the following command to start the app:

npx nodemon app.js

Now keep this shell running, and open a new one, and run the following command to make sure it’s running

curl -s http://localhost:3000/health | jq

This should show you this:

{
"health": "healthy"
}

Setup Sequelize

Run this command to initialize sequelize :

npx sequelize-cli init

This will create models, migrations , seeders and config directories.

Let’s organize the directories a little bit, by moving everything related to Data Access Layer (DAL) into a folder called dal :

mkdir dal && mv models dal && mv migrations dal && mv seeders dal && mv config/config.json dal && rm -rf config

After running this command, the project structure would be like this:

project new structure

Note that we removed to config folder and only moved the config.json file inside dal.

In order to connect to the database successfully, we need to setup config.json file with real database configurations.

In this article, for the sake of simplicity, I have already establised a Postgres Docker image and it’s ready to receive connections.

One last step to configure Sequelize, is to add a .sequelizerc file to the root directory

touch .sequelizerc

Inside that file, let’s define the paths to config, models, seeders and migrations:

const path = require('path');

module.exports = {
'config': path.resolve('dal', 'config.js'),
'models-path': path.resolve('dal', 'models'),
'seeders-path': path.resolve('dal', 'seeders'),
'migrations-path': path.resolve('dal', 'migrations')
}

To make sure your configuration is correct, let’s create the database using sequelize-cli:

npx sequelize-cli db:create 

If you received a success message, it means the environment is ready to start implementation.

To be continued

At this point we come to the end of part 1 of building a multi tenant app. Once part 2 is ready I will update this article with the link to part 2.

If you have any comments please let me know, and feel free to reach me out on LinkedIn, or Github.

The source code of the code above is available here.

--

--