A full-stack multi-tenant Notes application (backend + frontend) built with strict tenant isolation using a shared-schema approach.
This repository demonstrates multi-tenancy, authentication & authorization, tenant-scoped routing, and a complete frontend workflow.
- Overview
- Multi-Tenancy Approach
- Data Models
- Authentication & Authorization
- Backend Routes
- Seeded Tenants & Test Accounts
- Vercel Deployment
- Frontend Structure
- Local Setup
- API Endpoints Reference
- Security & Hardening
- Contact
This application implements a multi-tenant notes system where multiple organizations can sign up and manage their own notes.
- Strict isolation ensures that users from one tenant can never access another tenantβs data.
- Includes backend (Node.js/Express/MongoDB) and frontend (React/Vite).
- Comes with seeded tenants (
AcmeandGlobex) for local testing.
Approach: Shared schema with tenant identifier (tenant ObjectId).
- Simple, efficient, and works well with MongoDB + Mongoose.
- Matches existing code structure: models already include a
tenantfield. - Seed script supports multiple tenants (
Acme,Globex) out of the box.
- Schema-level
- All tenant-scoped models include
tenant: ObjectId (ref: Tenant)as a required field.
- All tenant-scoped models include
- Authentication Middleware
- Populates
req.userwith tenant info from JWT.
- Populates
- Route-level Filtering
- All queries filter by
tenant: req.user.tenant._id.
- All queries filter by
- Seed Validation
- Use provided tenants (Acme, Globex) to validate isolation.
name: String (required)slug: String (required, unique)plan: Enum (FREE,PRO), default:FREE
name: Stringemail: String (required, unique)password: String (hashed, required)role: Enum (ADMIN,MEMBER), default:MEMBERtenant: ObjectId (ref: Tenant, required)
title: Stringcontent: Stringtenant: ObjectId (ref: Tenant, required)createdBy: ObjectId (ref: User, required)
- Login:
POST /auth/loginβ returnsJWT + user + tenant info. - JWT Middleware: verifies token, attaches
req.userwith tenant. - Role Guard:
requireRole('ADMIN')β ensures admin-only access. - Change Password:
POST /auth/change-password.
| Route | Method | Description | Auth Required | Notes |
|---|---|---|---|---|
/auth/login |
POST | Authenticate user | β | Returns token + tenant info |
/auth/change-password |
POST | Change password | β | Verifies old password |
/notes |
GET | List notes | β | Tenant-scoped |
/notes |
POST | Create note | β | Enforces tenant + plan limit |
/notes/:id |
GET | View note | β | Tenant filter applied |
/notes/:id |
PUT | Update note | β | Tenant filter applied |
/notes/:id |
DELETE | Delete note | β | Tenant filter applied |
/tenants/:slug/upgrade |
POST | Upgrade plan | β (Admin) | Validates tenant slug |
/tenants/invite |
POST | Invite user | β (Admin) | Creates new user in tenant |
Run the seed script:
MONGO_URI="your-mongo-uri" JWT_SECRET="a-secret" node seed.js- Acme (slug:
acme, plan:FREE) - Globex (slug:
globex, plan:FREE)
admin@acme.testβ Admin (Acme)user@acme.testβ Member (Acme)admin@globex.testβ Admin (Globex)user@globex.testβ Member (Globex)
This project is configured for a monorepo deployment on Vercel, with separate projects for the backend and frontend.
- Create a free cluster on MongoDB Atlas.
- In the Network Access tab, add
0.0.0.0/0to the IP Access List.This is required to allow Vercel's serverless functions to connect.
- In the Database Access tab, create a database user and save the password.
- Get your connection string (Drivers β Node.js) and replace
<password>with your user's password.
This is yourMONGO_URI.
- Create a New Vercel Project: Import your Git repository.
- Configure Project:
- Project Name: e.g.,
notes-application-backend - Root Directory:
notes-backend
- Project Name: e.g.,
- Environment Variables:
MONGO_URI: The full connection string from MongoDB Atlas.JWT_SECRET: A secure, random string for signing tokens.
- Deploy: Vercel will use the
notes-backend/vercel.jsonfile to deploy the Express app as a serverless function.
Note the deployed URL (e.g.,https://your-backend.vercel.app).
- Create Another Vercel Project: Import the same Git repository again.
- Configure Project:
- Project Name: e.g.,
notes-application-frontend - Root Directory:
notes-frontend
- Project Name: e.g.,
- Environment Variables:
VITE_API_URL: The URL of your deployed backend from the previous step.
- Deploy: Vercel will use the
notes-frontend/vercel.jsonfile to correctly handle client-side routing.
Located in notes-frontend/src/:
- components/ β UI components (Navbar, NoteModal, InviteModal, etc.)
- context/ β
AuthContext,ToastContextfor global state & messages - lib/ β
api.js(centralized API helper) - pages/ β
LoginPage,NotesPage,UpgradePage - App.jsx β App bootstrap & routing
- User logs in β token stored in
AuthContext+localStorage. - All API calls attach
Authorization: Bearer <token>. - On
401 Unauthorized, user is logged out automatically.
cd notes-backend
npm install
# Required ENV variables
export MONGO_URI="your-mongo-uri"
export JWT_SECRET="your-secret"
npm run seed # seeds Acme & Globex
npm startcd notes-frontend
npm install
npm run dev
# App runs on http://localhost:5173 (default Vite port)POST /auth/loginβ{ token, user, tenant }POST /auth/change-passwordβ Change passwordGET /notesβ List tenant notesPOST /notesβ Create tenant note (enforces plan limits)GET /notes/:idβ View notePUT /notes/:idβ Update noteDELETE /notes/:idβ Delete notePOST /tenants/:slug/upgradeβ Upgrade plan (Admin only)POST /tenants/inviteβ Invite user (Admin only)
-
β Indexes:
{ tenant, createdAt }on notes,{ tenant, email }on users -
β JWT best practices: rotate keys, use HTTPS, HttpOnly cookies if needed
-
β Input validation: enforce via Joi / express-validator
-
β Rate limiting: mitigate brute force attacks
-
π Suggested:
- Tenant scoping middleware
- Mongoose plugin to auto-enforce tenant presence
Built and maintained by Ayush Kaneriya.
- GitHub: ayushkaneriya05
- LinkedIn: Ayush Kaneriya
- Email: ayushkaneriya05@gmail.com