Back to Blog

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

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:

java
businessname.yourapp.com

Example for a salon SaaS:

java
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:

java
saloonking.yourapp.com

The browser sends a request where:

java
req.hostname = saloonking.yourapp.com subdomain = saloonking

Your Express server checks the database:

java
subdomain "saloonking" → matches Tenant A

Then React loads that tenant’s website data.


PROJECT STRUCTURE (MERN)

java
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

java
const 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

java
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"));

  1. React Frontend Logic (Auto-Detect Subdomain)

frontend/src/App.js

java
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

java
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

java
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

java
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:

java
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:

java
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:

java
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:

TypeNameValue
A*.yourapp.comYour server IP
Ayourapp.comYour server IP

Example:

java
Type: A Host: * Value: 34.101.23.88 (your VPS IP) TTL: Auto

This means:

ANY subdomain → points to your backend server.

So:

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:

java
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.

Based in Greater Noida, IndiaCurrently