Building a Multi-Tenant React App. Part 1: Setup
Tags: react, webdev, javascript, node
Published: 2020-03-31 23:27:48

---
title: Building a Multi-Tenant React App. Part 1: Setup
published: true
date: 2020-03-31 00:00:00 UTC
tags: react, webdev, javascript, nodejs
canonical_url: https://www.delvalle.dev/posts/multi-tenant-react-app-part-1/multi-tenant-react-app-part-1/
cover_image: https://dev-to-uploads.s3.amazonaws.com/i/kwh1v1w9zr2f585vmm7o.jpg
---

Before getting into today's post, I am offering free sessions to React beginners. Wanna participate? You just have to follow me on [Twitter](https://twitter.com/jdelvx) and DM me telling your experience, what do you like about React or Web Dev in general and your goals as a Web Dev. More info [here](https://dev.to/jdelvx/i-am-offering-free-online-sessions-to-react-beginners-37j3).

* * *

Real-world scenario (cause it happened to me).

You work for a company that offers websites to different clients so they can showcase their products. All these websites share the same layout, the same core features and most of the components. There is some degree of flexibility allowed. For example, clients can choose the color theme and which routes to enable or disable. They can also choose which page or component to show on each route. Of course each client will have different content on their website.

The decision made is to only build one web application, flexible enough to handle these use cases instead of working on custom websites for each client. Clients are aware they'll share the layout and most of the styles.

In the end, we had about eight different clients. Two of them had two different sites. Each with a different color theme. We also ended up offering two different layouts and in some cases different components -_some clients wanted very specific functionalities_-.

How did we handle that? We received the client-specific configuration from the API and rendered everything dynamically client-side. Today I'll cover the project setup so we start with a good base. In future posts, I'll explain how we got to manage custom themes, custom routes, and custom components as per the clients' requests.

## [<svg aria-hidden="true" focusable="false" height="16" version="1.1" viewbox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg>](#initializing-the-project)Initializing the project

Let's first create our folder structure and our client-side app. I created a folder called **multi-tenant-react-app**. Let's open the terminal, go to this folder and do **npm init**. It will ask you to type some info. Here's what I entered -_the git repo is the one I'm gonna use for this series so you can leave this blank or add a repo of yours, I also called the entry point server.js instead of index.js_-.

```
package name: (multi-tenant-react-app) 
version: (1.0.0) 
description: A multi tenant React app with server
entry point: (index.js) server.js
test command: 
git repository: (https://github.com/dlvx/multi-tenant-react-app.git) 
keywords: 
author: José Del Valle
license: (ISC) 
```

Inside this same folder, we have to create our **server.js** file and initialize our react app with **create-react-app**. I'm gonna call it **client** because we are also going to work on a simple server using Node.

```
npx create-react-app client 
```

We should end up with the following structure:

```
- multi-tenant-react-app
  - /client
  - server.js
```

Remember **server.js** is outside of the **client** folder.

## [<svg aria-hidden="true" focusable="false" height="16" version="1.1" viewbox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg>](#creating-a-basic-server)Creating a basic server

Our server will serve the different config files to our client app so it knows what to render. For now, we'll install express to help us serve these config files. We are also gonna install the **cors** and **nodemon** packages. Make sure you're still in your root directory and do:

```
npm install express cors
```

You can choose to install nodemon globally or as a dev dependency. I have it globally.

```
npm install -g nodemon
```

Lets now add the script to run our server in the **package.json** file. Add the following line inside the **scripts** section:

```json
"server": "nodemon server.js",
```

Ok, let's now code our server. We first have to set it up so it listens on a specific port and also accepts requests from another origin as we'll run our React development server on a different port.

```js
const express = require('express');
const cors = require('cors');

// Setup
const PORT = process.env.SERVER_PORT || 4000;
const app = express();
app.use(cors());

// Routes
app.get('/getConfig', async function (req, res) {
  const { clientId } = req.query;
  /**
   * 
   * We'll add more code here later
   * 
  **/
});

// Run server
app.listen(PORT, () => {
  console.log(`Server listening on ${PORT}`);
});
```

We've added an endpoint called **getConfig** which will receive the client id from our react app and will return the config file specific to that client. For now, we're gonna handle the client id in our React app using environment variables but I'll return to that in a bit.

In the real world, we were storing the client configurations in a database but for this tutorial, I'm gonna stick with JSON just so you get the idea.

Let's create our JSON database now. Add a new folder called **db** , at the root level. Inside it lets add a file called **configs.json**. We should end up with the following structure:

```
- multi-tenant-react-app
  - /client
  - /db
    - configs.json
  - server.js
```

We'll now add some configuration for our clients in the configs.json file. Something simple for now so we can test this soon:

```json
[
  {
    "clientId": 1,
    "name": "Client A"
  },
  {
    "clientId": 1,
    "name": "Client B"
  }
]
```

## [<svg aria-hidden="true" focusable="false" height="16" version="1.1" viewbox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg>](#serving-client-specific-configuration)Serving client-specific configuration

Now, to serve each client configuration we need a way to get it from our JSON database and return it to the client-side app.

Let's add a new folder at the root level called **model** and create a new file inside it called **config.model.js**. For now, we'll add a simple function that will find the client config given an id:

```js
const configs = require('../db/configs.json');

function getClientConfig(clientId) {
  return configs.find(config => config.clientId == clientId);
}

module.exports = {
  getClientConfig
}
```

Now, in our **server.js** we'll import this model so we can use it in our **getConfig** endpoint.

```js
const Config = require('./model/config.model');
```

The **getConfig** endpoint will now look something like this:

```js
app.get('/getConfig', async function (req, res) {
  // Receive the clientId from our client-side app
  const { clientId } = req.query;
  
  // Find the config for that particular clientId
  const clientConfig = Config.getClientConfig(clientId);

  if(!clientConfig){
    // Return an error if it's not found
    res.status(404).send({ error: `Config not found for this clientId: ${clientId}` });
  }

  // Send the config if found
  res.send(clientConfig);
});
```

We are now ready to communicate with the client-side app and send the config it needs.

Lets finally move to our React app.

## [<svg aria-hidden="true" focusable="false" height="16" version="1.1" viewbox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg>](#receiving-the-configuration-in-our-react-app)Receiving the configuration in our React app

Go back to the terminal again and move to the **client** folder. Lets first run our app to make sure everything was set up correctly:

```
cd client && npm start
```

You should be able to see create-react-app's default home screen.

If everything's ok, let's install **axios** which will help us make requests to our server:

```
npm install axios
```

We need to create a new folder and some files. Create a folder called **services** and two files inside: **axiosSetup.js** and **config.service.js**.

Here's what we need to put in **axiosSetup.js** :

```js
import axios from 'axios';

const instance = axios.create({
  baseURL: `http://localhost:4000`,
});

export default instance;
```

Basically, we're just creating an instance that will communicate with our server running on port 4000. We export this instance so we can re-use it in our services.

As for **config.service.js** we need to add the function that will make the GET request through the axios instance:

```js
import axiosInstance from './axiosSetup';

async function getConfig(){
  try {
    return await axiosInstance.get('/getConfig', {
      params: {
        clientId: process.env.REACT_APP_CLIENT_ID
      }
    });
  } catch(e){
    return e.response;
  }
}

export {
  getConfig
}
```

We export the **getConfig** function so we can use it in **App.js**. As you can see, I'm sending the **clientId** to the server so it can find the correct config in the DB.

In **production** we didn't use environment variables to set the client id in the client-side but for the sake of the tutorial, it is just simpler this way.

One last step now. Lets go to **App.js** and import the **getConfig** service:

```js
import { getConfig } from './services/config.service';
```

Besides that, we need to import the **useEffect** and **useState** hooks, so your first line would be something like this:

```js
import React, { useState, useEffect } from 'react';
```

Inside our **App** component, we'll make use of **useEffect** to call **getConfig** when the component initially mounts and we'll use **useState** to manage our response status and store the config when available.

As for the JSX, we'll get rid of some things and add some status messages. It will show the **name** we set in each client config in our DB if the response is successful.

Our component will end up being like this:

```js
import React, { useState, useEffect } from 'react';
import logo from './logo.svg';
import './App.css';
import { getConfig } from './services/config.service';

function App() {

  const [config, setConfig] = useState({ loading: true, data: {} });
  const { loading, data } = config;

  useEffect(() => {
    async function getConfigAsync(){
      const { data } = await getConfig();
      setConfig({ data });
    }
  
    getConfigAsync();
  }
  , []);
  
  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <p>
          {
            loading && 'Getting config from server...'
          }
          {
            data.error && 'Error getting config from server'
          }
          {
            data.name && `The client is: ${data.name}`
          }
        </p>
      </header>
    </div>
  );
}

export default App;
```

## [<svg aria-hidden="true" focusable="false" height="16" version="1.1" viewbox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg>](#lets-try-this-out)Let's try this out!

We can finally run our app! Make sure you're still in the client folder.

We'll first test our error scenario. Let's just run the following command without setting a client id:

```
npm start
```

If everything was set correctly and the React app can communicate properly with the server you'll see the following message:

**Error getting config from server**.

It failed cause we didn't set a client id in the environment variables so the server didn't find the config for this app.

Now, stop the app and add the client id to the environment variables and run the app again with the following command. Like so:

```
REACT_APP_CLIENT_ID=1 npm start
```

What do you get? I hope you see the message: **The client is: Client A**

Now, what if you set the client id to 2 and run the following?

```
REACT_APP_CLIENT_ID=2 npm start
```

You should see: **The client is: Client B**

You can run both client apps simultaneously if you set a different port for one of them, for example:

```
REACT_APP_CLIENT_ID=1 npm start
```

and

```
PORT=3002 REACT_APP_CLIENT_ID=2 npm start
```

Both will communicate with the same server and will receive their specific config.

* * *

Ok! We're finally done for the moment. This is just the tip of the iceberg. We've only shown the client's name! In the next posts, we'll see how we can have different routes per client and then different component configurations.

Here's the [Github repo](https://github.com/dlvx/multi-tenant-react-app) in case you want the whole project.

Stay tuned and thanks for reading!

_Follow me on twitter: [@jdelvx](https://twitter.com/jdelvx)_