Question

I am using MongoDB with Nodejs application, how to setup database for multi tenancy SAAS application?

Hi, I am using managed MongoDB database and code is written in the Nodejs, I am planning to setup SAAS multi tenancy applicaiton. How I can setup database or how should be the in architecture?


Submit an answer


This textbox defaults to using Markdown to format your answer.

You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!

Sign In or Sign Up to Answer

These answers are provided by our Community. If you find them useful, show some love by clicking the heart. If you run into issues leave a comment, or add your own answer to help others.

Bobby Iliev
Site Moderator
Site Moderator badge
July 18, 2024

Hi there,

This is a great question! As with everything, there are multiple ways of doing this, I recently wrote an article on how to do this with Laravel and Postgres:

Creating a Multi-Tenant Application with Laravel and Postgres

The Laravel ecosystem offers multiple packages and different approaches on how to achieve this. For MongoDB and Node.js, I’d be happy to provide some insights and recommendations for your architecture:

  1. Database Architecture for Multi-Tenancy

When it comes to multi-tenancy, there are usually three main approaches:

a) Database per Tenant:

  • Create a separate MongoDB database for each tenant.
  • Pros: Strong isolation, easier to manage backups and scaling per tenant.
  • Cons: Can be resource-intensive with many tenants.

b) Collection per Tenant:

  • Use a single database but create separate collections for each tenant.
  • Pros: Better resource utilization than separate databases.
  • Cons: Less isolation, potential for naming conflicts. So you need to be careful on how you set that up!

c) Shared Collections with Tenant Field:

  • Use shared collections for all tenants, with a ‘tenantId’ field in each document.
  • Pros: Most efficient resource use, simplest to implement.
  • Cons: Requires careful query management to prevent data leaks so that users can’t see the records of other users.

For a SaaS application on DigitalOcean’s Managed MongoDB, I could suggest the “Shared Collections with Tenant Field” approach. This method balances scalability and simplicity. But at the end it comes to personal preferences of course.

  1. Node.js Application Architecture

Here’s a basic architecture for your Node.js application:

const mongoose = require('mongoose');
const express = require('express');

const app = express();

// Middleware to extract tenantId from request (e.g., from JWT or header)
const tenantMiddleware = (req, res, next) => {
  req.tenantId = extractTenantId(req);
  next();
};

app.use(tenantMiddleware);

// Connect to MongoDB
mongoose.connect('your_mongodb_connection_string');

// Define a schema with tenantId
const exampleSchema = new mongoose.Schema({
  tenantId: String,
  // other fields...
});

// Add a pre-find hook to automatically filter by tenantId
exampleSchema.pre('find', function() {
  this.where({ tenantId: this.options.tenantId });
});

const Example = mongoose.model('Example', exampleSchema);

// Route example
app.get('/data', async (req, res) => {
  const data = await Example.find().setOptions({ tenantId: req.tenantId });
  res.json(data);
});

app.listen(process.env.PORT || 3000);
  1. Deploying with DigitalOcean App Platform

App Platform makes it easy to deploy and scale your Node.js application:

  1. Create a new app in the App Platform dashboard.
  2. Connect your GitHub repository containing your Node.js application.
  3. Configure your app settings, including environment variables for your MongoDB connection string.
  4. Deploy your application.

App Platform can automatically handle scaling and load balancing for you.

Besides that, as with any application keep in mind the following:

  • Implement proper authentication and authorization to ensure tenants can only access their own data.
  • Use DigitalOcean’s monitoring tools to keep an eye on your database and application performance.
  • Implement caching (e.g., with Redis) to improve performance for frequently accessed data.
  • Consider using DigitalOcean’s managed Kubernetes service for more complex deployments as your application grows.

Besides the above, here is an example on how you could achieve this architecture on the database side, we’ll use a single MongoDB database with shared collections and a tenant field for data isolation.

  1. Collections:

a) users b) books c) tenants

  1. Example Schema Definitions:

a) Tenants Collection:

{
  _id: ObjectId,
  name: String,
  subdomain: String,
  createdAt: Date,
  settings: {
    maxUsers: Number,
    maxBooks: Number,
    // other tenant-specific settings
  }
}

b) Users Collection:

{
  _id: ObjectId,
  tenantId: ObjectId,  // Reference to tenant
  email: String,
  password: String,  // Hashed
  name: String,
  role: String,
  createdAt: Date
}

c) Books Collection:

{
  _id: ObjectId,
  tenantId: ObjectId,  // Reference to tenant
  title: String,
  author: String,
  isbn: String,
  publishedDate: Date,
  createdAt: Date,
  updatedAt: Date
}
  1. Indexing:

To optimize query performance, you should create indexes on frequently queried fields:

db.users.createIndex({ tenantId: 1, email: 1 }, { unique: true })
db.books.createIndex({ tenantId: 1, isbn: 1 }, { unique: true })
db.tenants.createIndex({ subdomain: 1 }, { unique: true })
  1. Query Examples:

When querying the database, always include the tenantId to ensure data isolation:

// Get all books for a specific tenant
db.books.find({ tenantId: ObjectId("tenant_id_here") })

// Get a specific user for a tenant
db.users.findOne({ tenantId: ObjectId("tenant_id_here"), email: "user@example.com" })
  1. Data Access Layer:

In your Node.js application, you can create a data access layer that automatically includes the tenantId in all queries. Here’s a simple example:

class BookService {
  constructor(tenantId) {
    this.tenantId = tenantId;
  }

  async getAllBooks() {
    return await db.books.find({ tenantId: this.tenantId }).toArray();
  }

  async addBook(bookData) {
    const book = { ...bookData, tenantId: this.tenantId, createdAt: new Date() };
    return await db.books.insertOne(book);
  }

  // Other methods...
}

// Usage in your application
app.get('/books', async (req, res) => {
  const bookService = new BookService(req.tenantId);
  const books = await bookService.getAllBooks();
  res.json(books);
});
  1. Tenant Initialization:

When a new tenant signs up, you’d create a new document in the tenants collection and set up any initial data they need:

async function initializeTenant(tenantData) {
  const session = await db.startSession();
  session.startTransaction();

  try {
    const tenant = await db.tenants.insertOne(tenantData, { session });
    
    // Create an admin user for the tenant
    await db.users.insertOne({
      tenantId: tenant.insertedId,
      email: tenantData.adminEmail,
      password: hashedPassword,
      role: 'admin',
      createdAt: new Date()
    }, { session });

    await session.commitTransaction();
    return tenant.insertedId;
  } catch (error) {
    await session.abortTransaction();
    throw error;
  } finally {
    session.endSession();
  }
}

If you prefer to use separate databases, here is a good article on how you could do that:

https://medium.com/geekculture/building-a-multi-tenant-app-with-nodejs-mongodb-ec9b5be6e737

Hope that this helps!

- Bobby

Try DigitalOcean for free

Click below to sign up and get $200 of credit to try our products over 60 days!

Sign up

Become a contributor for community

Get paid to write technical tutorials and select a tech-focused charity to receive a matching donation.

DigitalOcean Documentation

Full documentation for every DigitalOcean product.

Resources for startups and SMBs

The Wave has everything you need to know about building a business, from raising funding to marketing your product.

Get our newsletter

Stay up to date by signing up for DigitalOcean’s Infrastructure as a Newsletter.

New accounts only. By submitting your email you agree to our Privacy Policy

The developer cloud

Scale up as you grow — whether you're running one virtual machine or ten thousand.

Get started for free

Sign up and get $200 in credit for your first 60 days with DigitalOcean.*

*This promotional offer applies to new accounts only.