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

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:
javabusinessname.yourapp.com
Example for a salon SaaS:
javasaloonking.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:
javasaloonking.yourapp.com
The browser sends a request where:
javareq.hostname = saloonking.yourapp.com subdomain = saloonking
Your Express server checks the database:
javasubdomain "saloonking" → matches Tenant A
Then React loads that tenant’s website data.
PROJECT STRUCTURE (MERN)
javaroot/ 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
javaconst mongoose = require("mongoose"); const tenantSchema = new mongoose.Schema({ name: String, subdomain: String, siteData: Object }); module.exports = mongoose.model("Tenant", tenantSchema);
Example record:
java{ "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
javaconst 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
javaimport 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
javaimport { 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
javaexport 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
javaexport 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:
java127.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:
javahttp://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:
javasaloonname.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:
javaType: 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 app -
saloon2.yourapp.com→ goes to your Express app -
xyz.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:
javaconst 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.