How to Build a Multi-Tenant SaaS Application with the MERN Stack

Multi-Tenant SaaS (Software as a Service) applications allow multiple users (tenants) to share the same infrastructure while maintaining isolated data and configurations. In this guide, we will explore how to build a multi-tenant SaaS application using the MERN (MongoDB, Express, React, Node.js) stack. 1. Understanding Multi-Tenancy Types of Multi-Tenancy: Single Database, Shared Schema: All tenants share the same database and collections, with tenant-specific data identified by a tenantId. Single Database, Separate Schemas: A single database is used, but each tenant has its own schema or set of collections. Separate Databases for Each Tenant: Each tenant gets its own database, providing maximum isolation but higher resource costs. For this guide, we will use the Single Database, Shared Schema approach for scalability and cost efficiency. 2. Setting Up the MERN Stack Install Dependencies npm install express mongoose jsonwebtoken bcryptjs cors dotenv 3. Implementing Tenant Identification Each request must identify the tenant and ensure data is scoped correctly. Middleware to Identify Tenant Create a middleware to extract the tenant ID from the request: const tenantMiddleware = (req, res, next) => { const tenantId = req.headers['x-tenant-id']; if (!tenantId) { return res.status(400).json({ error: 'Tenant ID is required' }); } req.tenantId = tenantId; next(); }; export default tenantMiddleware; 4. Designing the Database Schema Using a shared schema, we will add a tenantId field to each document. User Model (MongoDB + Mongoose) import mongoose from 'mongoose'; const userSchema = new mongoose.Schema({ tenantId: { type: String, required: true }, name: { type: String, required: true }, email: { type: String, required: true, unique: true }, password: { type: String, required: true } }); const User = mongoose.model('User', userSchema); export default User; 5. Multi-Tenant Authentication Register Users for Specific Tenants import express from 'express'; import bcrypt from 'bcryptjs'; import jwt from 'jsonwebtoken'; import User from '../models/User.js'; import tenantMiddleware from '../middleware/tenantMiddleware.js'; const router = express.Router(); router.post('/register', tenantMiddleware, async (req, res) => { try { const { name, email, password } = req.body; const hashedPassword = await bcrypt.hash(password, 10); const user = new User({ tenantId: req.tenantId, name, email, password: hashedPassword }); await user.save(); res.status(201).json({ message: 'User registered successfully' }); } catch (error) { res.status(500).json({ error: error.message }); } }); export default router; Login and Generate JWT Token router.post('/login', tenantMiddleware, async (req, res) => { try { const { email, password } = req.body; const user = await User.findOne({ email, tenantId: req.tenantId }); if (!user) return res.status(400).json({ error: 'Invalid credentials' }); const isMatch = await bcrypt.compare(password, user.password); if (!isMatch) return res.status(400).json({ error: 'Invalid credentials' }); const token = jwt.sign({ userId: user._id, tenantId: user.tenantId }, 'secret', { expiresIn: '1h' }); res.json({ token }); } catch (error) { res.status(500).json({ error: error.message }); } }); 6. Building the Frontend with React Handle Tenant-Based API Requests Ensure that each request includes the tenant ID: const apiClient = axios.create({ baseURL: 'http://localhost:5000', headers: { 'x-tenant-id': 'your-tenant-id', 'Authorization': `Bearer ${localStorage.getItem('token')}` } }); Tenant-Specific Dashboards Each tenant should see only their relevant data. Use React Context or Redux for state management. 7. Deployment Considerations Hosting the Backend Use Render, DigitalOcean, or AWS for deploying the Express backend. Use MongoDB Atlas for cloud-based database storage. Hosting the Frontend Deploy the React app on Vercel or Netlify. Security Best Practices Use JWT authentication to protect API endpoints. Validate tenantId in every request. Encrypt sensitive user data. Conclusion Building a multi-tenant SaaS application using the MERN stack requires careful planning around tenant identification, authentication, and data isolation. By following the steps in this guide, you can create a scalable and secure SaaS application that serves multiple clients efficiently. Start building your multi-tenant MERN SaaS today!

Feb 6, 2025 - 12:56
 0
How to Build a Multi-Tenant SaaS Application with the MERN Stack

Multi-Tenant SaaS (Software as a Service) applications allow multiple users (tenants) to share the same infrastructure while maintaining isolated data and configurations. In this guide, we will explore how to build a multi-tenant SaaS application using the MERN (MongoDB, Express, React, Node.js) stack.

1. Understanding Multi-Tenancy

Types of Multi-Tenancy:

  • Single Database, Shared Schema: All tenants share the same database and collections, with tenant-specific data identified by a tenantId.
  • Single Database, Separate Schemas: A single database is used, but each tenant has its own schema or set of collections.
  • Separate Databases for Each Tenant: Each tenant gets its own database, providing maximum isolation but higher resource costs.

For this guide, we will use the Single Database, Shared Schema approach for scalability and cost efficiency.

2. Setting Up the MERN Stack

Install Dependencies

npm install express mongoose jsonwebtoken bcryptjs cors dotenv

3. Implementing Tenant Identification

Each request must identify the tenant and ensure data is scoped correctly.

Middleware to Identify Tenant

Create a middleware to extract the tenant ID from the request:

const tenantMiddleware = (req, res, next) => {
  const tenantId = req.headers['x-tenant-id'];
  if (!tenantId) {
    return res.status(400).json({ error: 'Tenant ID is required' });
  }
  req.tenantId = tenantId;
  next();
};

export default tenantMiddleware;

4. Designing the Database Schema

Using a shared schema, we will add a tenantId field to each document.

User Model (MongoDB + Mongoose)

import mongoose from 'mongoose';

const userSchema = new mongoose.Schema({
  tenantId: { type: String, required: true },
  name: { type: String, required: true },
  email: { type: String, required: true, unique: true },
  password: { type: String, required: true }
});

const User = mongoose.model('User', userSchema);
export default User;

5. Multi-Tenant Authentication

Register Users for Specific Tenants

import express from 'express';
import bcrypt from 'bcryptjs';
import jwt from 'jsonwebtoken';
import User from '../models/User.js';
import tenantMiddleware from '../middleware/tenantMiddleware.js';

const router = express.Router();

router.post('/register', tenantMiddleware, async (req, res) => {
  try {
    const { name, email, password } = req.body;
    const hashedPassword = await bcrypt.hash(password, 10);

    const user = new User({
      tenantId: req.tenantId,
      name,
      email,
      password: hashedPassword
    });

    await user.save();
    res.status(201).json({ message: 'User registered successfully' });
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});

export default router;

Login and Generate JWT Token

router.post('/login', tenantMiddleware, async (req, res) => {
  try {
    const { email, password } = req.body;
    const user = await User.findOne({ email, tenantId: req.tenantId });
    if (!user) return res.status(400).json({ error: 'Invalid credentials' });

    const isMatch = await bcrypt.compare(password, user.password);
    if (!isMatch) return res.status(400).json({ error: 'Invalid credentials' });

    const token = jwt.sign({ userId: user._id, tenantId: user.tenantId }, 'secret', { expiresIn: '1h' });
    res.json({ token });
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});

6. Building the Frontend with React

Handle Tenant-Based API Requests

Ensure that each request includes the tenant ID:

const apiClient = axios.create({
  baseURL: 'http://localhost:5000',
  headers: {
    'x-tenant-id': 'your-tenant-id',
    'Authorization': `Bearer ${localStorage.getItem('token')}`
  }
});

Tenant-Specific Dashboards

Each tenant should see only their relevant data. Use React Context or Redux for state management.

7. Deployment Considerations

Hosting the Backend

  • Use Render, DigitalOcean, or AWS for deploying the Express backend.
  • Use MongoDB Atlas for cloud-based database storage.

Hosting the Frontend

  • Deploy the React app on Vercel or Netlify.

Security Best Practices

  • Use JWT authentication to protect API endpoints.
  • Validate tenantId in every request.
  • Encrypt sensitive user data.

Conclusion

Building a multi-tenant SaaS application using the MERN stack requires careful planning around tenant identification, authentication, and data isolation. By following the steps in this guide, you can create a scalable and secure SaaS application that serves multiple clients efficiently.

Start building your multi-tenant MERN SaaS today!