Building a Pseudo Multi-Tenant App in Strapi: Step-by-Step
Tags: development, multitenancy, strapi
Published: 2023-02-22 15:53:19


In Strapi, a multitenant approach refers to the ability of the system to serve multiple tenants or customers, each with its own isolated and independent set of resources such as content, users, and permissions. It allows you to create multiple application instances using a single codebase. In this way, all clients have a separate database, theming, and separate domain for each Strapi instance. The advantage of running multitenant is you can roll out the new feature easily to all clients easily and it involves low maintenance with a scalable solution.

This tutorial will show how to run multiple Strapi applications sharing the same code.  This is not the complete approach for multitenancy. But it solves some problems, like rolling the same features to multiple websites simultaneously. I have been running around ten applications, such as driving school, jewelry shop, personal portfolio, and educational consultancy.

The approach is simple. I have a separate database for each instance or website, but they all run from a single code base. So it somehow acts as a multi-tenant. I have been able to manage different themes for each client but can roll out the same features for all instances at once.

## Prerequisites
- [nginx](https://www.digitalocean.com/community/tutorials/how-to-install-nginx-on-ubuntu-20-04) 
- [Let’s Encrypt for SSL](https://www.digitalocean.com/community/tutorials/how-to-secure-nginx-with-let-s-encrypt-on-ubuntu-20-04)
- [MySQL Database](https://www.mysql.com/)
- [phpMyAdmin (DB management )](https://www.phpmyadmin.net/)
- [Netlify](https://www.netlify.com/) ( For Frontend )
- [Node.js](https://nodejs.org/en/download/) v16+
- [Ubuntu 20.04](https://ubuntu.com/download/server)


## Create a Strapi Application

Create a Strapi project running the following command.

```bash
     npx create-strapi-app@latest strapi-multitenancy
```

![Terminal](https://paper-attachments.dropboxusercontent.com/s_A7E9C5168B635AA264F39520B2754C3A2B2AC8805B2EE13368EAF23831E6113E_1674128455543_image.png)


Once all the dependencies are installed, it should be up and running at [http://localhost:1337/](http://localhost:1337/)
By default, Strapi runs on the SQLite database. Stop the app and let’s change the database to MySQL.

## Add MySQL 

Navigate to the directory where you created strapi-multitenancy (or your custom project name). Then run the following command to add the MYSQL package.

```bash
     npm install mysql --save
```

## Changing Strapi Config

Initially, the project structure and config folder should look like this: 

![Project Structure](https://paper-attachments.dropboxusercontent.com/s_A7E9C5168B635AA264F39520B2754C3A2B2AC8805B2EE13368EAF23831E6113E_1674129090133_image.png)

Suppose we want to run three website development, site1, and site2. Then the new config structure should look like the one below: 

![Config Structure](https://paper-attachments.dropboxusercontent.com/s_A7E9C5168B635AA264F39520B2754C3A2B2AC8805B2EE13368EAF23831E6113E_1674129256348_image.png)

By default, all the files inside the directory are the same as the files being created.

## Creating MySQL Database

I assume that you have a MySQL server running. Let's create three databases: 

- development
- site1
- site2

Keep the database username and password safe for later use.

## Update Database Configuration

After creating the database and config directory, let's modify the database configuration.
The default database.js file is as below.

```js
    const path = require('path');
    
    module.exports = ({ env }) => ({
      connection: {
        client: 'sqlite',
        connection: {
          filename: path.join(__dirname, '..', env('DATABASE_FILENAME', '.tmp/data.db')),
        },
        useNullAsDefault: true,
      },
    });
```

We want to change this to MySQL and each website specific. In my environment, the database username is root and the database password is root. 

Now you will update site1 and site2 as below. 

The database config for site1: 
```js
    module.exports = ({ env }) => ({
      connection: {
        client: 'mysql',
        connection: {
          host: env('DATABASE_HOST', 'localhost'),
          port: env.int('DATABASE_PORT', 3306),
          database: env('DATABASE_NAME', 'site1'),
          user: env('DATABASE_USERNAME', 'root'),
          password: env('DATABASE_PASSWORD', 'root'),
          ssl: env.bool('DATABASE_SSL', false),
        },
      },
    });
```

The database config for site2:
```js
    module.exports = ({ env }) => ({
      connection: {
        client: 'mysql',
        connection: {
          host: env('DATABASE_HOST', 'localhost'),
          port: env.int('DATABASE_PORT', 3306),
          database: env('DATABASE_NAME', 'site2'),
          user: env('DATABASE_USERNAME', 'root'),
          password: env('DATABASE_PASSWORD', 'root'),
          ssl: env.bool('DATABASE_SSL', false),
        },
      },
    });
```
## Update Host Configuration

Now we need to specify different host ports for running three applications. The default server.js file is as below, which we need to update.

```js
    module.exports = ({ env }) => ({
      host: env('HOST', '0.0.0.0'),
      port: env.int('PORT', 1337),
      app: {
        keys: env.array('APP_KEYS'),
      },
    });
```
Let’s modify for app host. This is a configuration for app site1: 
```js

    module.exports = ({ env }) => ({
      host: env('HOST', '0.0.0.0'),
      port: env.int('HOST_PORT_SITE1', 4338),
      app: {
        keys: env.array('APP_KEYS'),
      },
    });
    
```

This is a configuration for app site2:
```js
    module.exports = ({ env }) => ({
      host: env('HOST', '0.0.0.0'),
      port: env.int('HOST_PORT_SITE2', 4339),
      app: {
        keys: env.array('APP_KEYS'),
      },
    });

```

## Install PM2 Runtime

We will be using  PM2 for running our Strapi application.

Install PM2 by running:
```bash
    npm install pm2@latest -g
```

The pm2 configuration file must be located at the root level and the filename as ecosystem.config.js. 

The pm2 configuration is as below.
```js
    module.exports = {
      apps: [
        {
          name: 'site1',
          cwd: '/Users/bikramkawan/Bikram/strapi-multitenancy-v4',
          script: 'npm',
          args: 'start',
          env: {
            NODE_ENV: 'site1',
            HOST_PORT_SITE1: 4338,
            DOMAIN_URL: 'site1.example.com'
          }
        },
        {
          name: 'site2',
          cwd: '/Users/bikramkawan/Bikram/strapi-multitenancy-v4',
          script: 'npm',
          args: 'start',
          env: {
            NODE_ENV: 'site2',
            HOST_PORT_SITE2: 4339,
            DOMAIN_URL: 'site2.example.com'
          }
        }
      ]
    };
    
```

From the above configuration, you need to change the correct path where your application is: 
- *cwd -> Change to the directory where you create* *the S**trapi application*
- *Database credentials*
- *Host ports*
- *DOMAIN_URL is for* *a* *fully qualified domain like strapimultitest1.example.com*

## Testing on Local Environment (localhost)
At this point, the app should be running on your local environment you are doing on your local machine. It should accessible via localhost or either public IP.

Run the app running this command on the root: 
```bash
    pm2 start ecosystem.config.js
```

Ideally, you should be able to access http://localhost:4338/ and http://localhost:4339/. 

For more documentation on pm2, you can read from [here](https://pm2.keymetrics.io/docs/usage/quick-start/)

## Configuring nginx for Live (Production)
Based on your distro, you need to install nginx correctly. I am running ubuntu 20.04. Please feel free to follow any documentation for installing nginx on your distro. I followed the [D](https://www.digitalocean.com/community/tutorials/how-to-install-nginx-on-ubuntu-20-04)[igital](https://www.digitalocean.com/community/tutorials/how-to-install-nginx-on-ubuntu-20-04)[O](https://www.digitalocean.com/community/tutorials/how-to-install-nginx-on-ubuntu-20-04)[cean article](https://www.digitalocean.com/community/tutorials/how-to-install-nginx-on-ubuntu-20-04) for installing nginx.

It is important to make sure that port 80 and port 443 (for SSL) as open. After successfully installing, we will configure the correct file. 

Go to cd /etc/nginx/sites-available, then create the file for site1.example.com using vi strapimultitest1.example.com. Simply vi /etc/nginx/sites-available/site1.example.com and paste the following.  Change the corresponding port depending on your application server 127.0.0.1:4338;

```js
    upstream site1.example.com {
      server 127.0.0.1:4338;
      keepalive 64;
    }
    
    server {
      server_name site1.example.com;
      access_log /var/log/nginx/site1.example.com-access.log;
      error_log /var/log/nginx/site1.example.com-error.log;
      location / {
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header Host $http_host;
        proxy_set_header X-NginX-Proxy true;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_pass http://site1.example.com;
        proxy_redirect off;
        proxy_http_version 1.1;
        proxy_cache_bypass $http_upgrade;
      }
    }
    
    server {
      listen 80;
      server_name site1.example.com;
    }
    
    
    server {
      server_name site1.example.com;
        listen 80;
        return 404; 
    }
```

We need to create another file for site2 as: 
```js
    upstream site2.example.com {
      server 127.0.0.1:4339;
      keepalive 64;
    }
    
    server {
      server_name site2.example.com;
      access_log /var/log/nginx/site2.example.com-access.log;
      error_log /var/log/nginx/site2.example.com-error.log;
      location / {
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header Host $http_host;
        proxy_set_header X-NginX-Proxy true;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_pass http://site2.example.com;
        proxy_redirect off;
        proxy_http_version 1.1;
        proxy_cache_bypass $http_upgrade;
      }
    }
    
    server {
    
      listen 80;
      server_name site2.example.com;
    
    }
    
    
    server {
      server_name site2.example.com;
        listen 80;
        return 404; 
    
    }

```

After creating nginx config file then, run:
```js
    sudo ln -s /etc/nginx/sites-available/site1.example.com /etc/nginx/sites-enabled/site1.example.com
    
    sudo ln -s /etc/nginx/sites-available/site2.example.com /etc/nginx/sites-enabled/site2.example.com
```
Check if nginx config is ok by running:
```bash
    sudo nginx -t
```

Restart nginx:
```bash
    sudo systemctl restart nginx
```

If port 80 is open, both applications site1.example.com and site2.example.com should be accessible.

## Install SSL Certificate
Let's secure our application and install SSL certificate. Check the Strapi [documentation](https://docs.strapi.io/developer-docs/latest/setup-deployment-guides/deployment/hosting-guides/amazon-aws.html) to install SSL certificate. 

Run the following command: 
```bash
    sudo apt install certbot python3-certbot-nginx -y
```

Now we need to add our domains: 
```bash
    certbot --nginx -d site1.example.com -d site2.example.com
```

It should now be running SSL on both of the domains.

## Conclusion 

In this tutorial, we learn how to create a multitenant on Strapi. This means you can run multiple Strapi instances with separate domains and separate databases. But with the same code base. From this approach, we will save time on maintaining applications and easy to roll out new features easily.

Here is the [GitHub](https://github.com/bikramkawan/strapi-multitenancy-v4) [repo](https://github.com/bikramkawan/strapi-multitenancy-v4) I have created for the approach you can test on your own at localhost or production.

You can join the [Discord community](https://discord.com/invite/strapi) to connect with more people using Strapi. You can ask for help or get answers to your question from the community.
 
Let me know if you run into an issue in the comments. Thank you!