Building a Multi-Tenant SaaS With Automatic Subdomains Using MERN Stack (2025 Guide)

Hi there! I'm Aditya, a passionate Full-Stack Developer driven by a love for turning concepts into captivating digital experiences. With a blend of creativity and technical expertise, I specialize in crafting user-friendly websites and applications that leave a lasting impression. Let's connect and bring your digital vision to life!
Modern SaaS applications—LMS platforms, CRM tools, appointment management systems—often need multi-tenant architecture. A powerful (and popular) approach is subdomain-based multi-tenancy, where each business gets:
businessname.yourapp.com
Example for a salon SaaS:
saloonking.glowSaaS.com
prettylooks.glowSaaS.com
divasalon.glowSaaS.com
In this guide, we will build a working subdomain multi-tenant system using:
MongoDB
Express.js
React
CORS
Local host mapping
DNS configuration for production
Everything is simplified so you can extend later.
How Subdomain Multi-Tenancy Works
When someone enters:
saloonking.yourapp.com
The browser sends a request where:
req.hostname = saloonking.yourapp.com
subdomain = saloonking
Your Express server checks the database:
subdomain "saloonking" → matches Tenant A
Then React loads that tenant’s website data.
PROJECT STRUCTURE (MERN)
root/
backend/
server.js
models/Tenant.js
frontend/
src/
pages/
TenantSite.jsx
MainSite.jsx
Dashboard.jsx
App.js
Backend port: 5000
Frontend port: 3000
1. MongoDB Tenant Model
backend/models/Tenant.js
const mongoose = require("mongoose");
const tenantSchema = new mongoose.Schema({
name: String,
subdomain: String,
siteData: Object
});
module.exports = mongoose.model("Tenant", tenantSchema);
Example record:
{
"name": "Saloon King",
"subdomain": "saloonking",
"siteData": {
"title": "Welcome to Saloon King",
"description": "Best salon in town"
}
}
2. Express Backend With Subdomain Detection
backend/server.js
const express = require("express");
const mongoose = require("mongoose");
const cors = require("cors");
const Tenant = require("./models/Tenant");
const app = express();
app.use(cors());
app.use(express.json());
mongoose.connect("mongodb://127.0.0.1:27017/multitenant");
// Middleware: detect subdomain
app.use(async (req, res, next) => {
const host = req.hostname; // e.g., saloonking.yourapp.com
const parts = host.split(".");
const sub = parts[0];
if (sub !== "localhost" && sub !== "yourapp" && sub !== "app") {
const tenant = await Tenant.findOne({ subdomain: sub });
if (tenant) req.tenant = tenant;
}
next();
});
// API to return tenant data
app.get("/tenant", (req, res) => {
if (!req.tenant) return res.json({ tenant: null });
res.json(req.tenant);
});
app.listen(5000, () => console.log("Backend running on port 5000"));
React Frontend Logic (Auto-Detect Subdomain)
frontend/src/App.js
import TenantSite from "./pages/TenantSite";
import MainSite from "./pages/MainSite";
import Dashboard from "./pages/Dashboard";
function App() {
const hostname = window.location.hostname.split(".")[0];
if (hostname === "app") return <Dashboard />;
if (hostname === "yourapp" || hostname === "localhost")
return <MainSite />;
return <TenantSite />;
}
export default App;
📄 Tenant Website Page
frontend/src/pages/TenantSite.jsx
import { useEffect, useState } from "react";
export default function TenantSite() {
const [tenant, setTenant] = useState(null);
useEffect(() => {
fetch("http://localhost:5000/tenant")
.then(res => res.json())
.then(data => setTenant(data));
}, []);
if (!tenant) return <h2>Loading...</h2>;
return (
<div style={{ padding: 20 }}>
<h1>{tenant.siteData.title}</h1>
<p>{tenant.siteData.description}</p>
</div>
);
}
🏠 Main SaaS Homepage
frontend/src/pages/MainSite.jsx
export default function MainSite() {
return (
<div style={{ padding: 20 }}>
<h1>Welcome to My SaaS</h1>
<p>Create your own salon website in minutes.</p>
</div>
);
}
🧑💼 Tenant Dashboard
frontend/src/pages/Dashboard.jsx
export default function Dashboard() {
return (
<div style={{ padding: 20 }}>
<h1>Tenant Dashboard</h1>
<p>Edit your website here</p>
</div>
);
}
🧪 4. Local Testing: Configure /etc/hosts
Add:
127.0.0.1 saloonking.localhost
127.0.0.1 prettylooks.localhost
127.0.0.1 yourapp.localhost
127.0.0.1 app.localhost
Now test:
http://saloonking.localhost:3000
You will see that salon’s website.
🌐 5. DNS + Production Setup: Allow Users to Create Subdomains Automatically
This is the MOST IMPORTANT part you requested.
This lets your SaaS automatically create:
saloonname.yourapp.com
✔ Only ONE DNS record is required:
✅ Step 1: Add a Wildcard DNS Record
Go to your domain provider (GoDaddy / Hostinger / Cloudflare).
Add:
| Type | Name | Value |
| A | *.yourapp.com | Your server IP |
| A | yourapp.com | Your server IP |
Example:
Type: A
Host: *
Value: 34.101.23.88 (your VPS IP)
TTL: Auto
This means:
ANY subdomain → points to your backend server.
So:
saloon1.yourapp.com→ goes to your Express appsaloon2.yourapp.com→ goes to your Express appxyz.yourapp.com→ also goes to your Express app
You do NOT need to create each subdomain manually.
🎉 This is how SaaS platforms like Shopify, Wix, and Notion do it.
🏗 Step 2: When a salon signs up, generate a subdomain
In your signup logic:
const newTenant = new Tenant({
name: req.body.name,
subdomain: req.body.subdomain,
siteData: { title: "", description: "" }
});
await newTenant.save();
Now saloonName.yourapp.com will start working immediately because of the wildcard DNS.
🔒 Step 3: SSL (HTTPS)
For HTTPS, use:
✔ Caddy (best and automatic)
or
✔ Certbot + NGINX
or
✔ Cloudflare proxy
Caddy auto-generates SSL for ALL wildcard subdomains.
