Multi-Tenant Implementation Summary
Completed Updates
✅ Models with tenantId Added
- credential.model.ts - Already implemented
- page.model.ts - ✅ Complete with tenant-scoped indexes
- product.model.ts - ✅ Complete with tenant-scoped indexes
- websiteManagement.model.ts - ✅ Complete (singleton per tenant)
- command.model.ts - ✅ Complete with tenant-scoped indexes
Remaining Models (31 models)
Quick Reference: Standard Pattern
// 1. ADD TENANT FIELD (after imports, as first field in schema)
const ModelSchema = new Schema<IModel>({
tenantId: {
type: Schema.Types.ObjectId,
ref: "Tenant",
required: true,
index: true,
},
// ... rest of fields
});
// 2. ADD TENANT-SCOPED INDEXES (before export)
// Basic indexes (required for all)
ModelSchema.index({ tenantId: 1, createdAt: -1 });
// Unique fields per tenant (if applicable)
ModelSchema.index({ tenantId: 1, slug: 1 }, { unique: true });
ModelSchema.index({ tenantId: 1, email: 1 }, { unique: true });
// Active filter (if has 'active' field)
ModelSchema.index({ tenantId: 1, active: 1 });
// Additional business logic indexes
// Add any other frequently queried fields
Models by Category with Specific Instructions
Content Management
-
news.model.ts
- Indexes:
{ tenantId: 1, slug: 1 }unique,{ tenantId: 1, active: 1 },{ tenantId: 1, publicationDate: -1 }
- Indexes:
-
gallery.model.ts
- Indexes:
{ tenantId: 1, slug: 1 }unique,{ tenantId: 1, active: 1 }
- Indexes:
-
file.model.ts
- Indexes:
{ tenantId: 1, createdAt: -1 },{ tenantId: 1, filename: 1 }
- Indexes:
E-commerce
-
category.model.ts
- Indexes:
{ tenantId: 1, slug: 1 }unique,{ tenantId: 1, parent: 1 },{ tenantId: 1, active: 1 }
- Indexes:
-
review.model.ts
- Indexes:
{ tenantId: 1, productId: 1 },{ tenantId: 1, approved: 1 },{ tenantId: 1, rating: 1 }
- Indexes:
-
cart.model.ts
- Indexes:
{ tenantId: 1, userId: 1 },{ tenantId: 1, sessionId: 1 },{ tenantId: 1, updatedAt: -1 }
- Indexes:
-
giftCard.model.ts
- Indexes:
{ tenantId: 1, code: 1 }unique,{ tenantId: 1, status: 1 },{ tenantId: 1, expiresAt: 1 }
- Indexes:
Hospitality
-
room.model.ts
- Indexes:
{ tenantId: 1, slug: 1 }unique,{ tenantId: 1, active: 1 },{ tenantId: 1, roomType: 1 }
- Indexes:
-
event.model.ts
- Indexes:
{ tenantId: 1, slug: 1 }unique,{ tenantId: 1, startDate: 1 },{ tenantId: 1, active: 1 }
- Indexes:
-
vehicle.model.ts
- Indexes:
{ tenantId: 1, slug: 1 }unique,{ tenantId: 1, active: 1 }
- Indexes:
Real Estate
- realEstateProperty.model.ts
- Indexes:
{ tenantId: 1, slug: 1 }unique,{ tenantId: 1, status: 1 },{ tenantId: 1, price: 1 }
- Indexes:
Food & Beverage
- wine.model.ts
- Indexes:
{ tenantId: 1, slug: 1 }unique,{ tenantId: 1, active: 1 },{ tenantId: 1, vintage: 1 }
- Indexes:
Communication
-
contact.model.ts
- Indexes:
{ tenantId: 1, createdAt: -1 },{ tenantId: 1, status: 1 },{ tenantId: 1, email: 1 }
- Indexes:
-
newsletter.model.ts
- Indexes:
{ tenantId: 1, slug: 1 }unique,{ tenantId: 1, status: 1 },{ tenantId: 1, sendDate: 1 }
- Indexes:
-
newsletterSubscribers.model.ts
- Indexes:
{ tenantId: 1, email: 1 }unique,{ tenantId: 1, subscribed: 1 }
- Indexes:
-
group.model.ts
- Indexes:
{ tenantId: 1, slug: 1 }unique,{ tenantId: 1, active: 1 }
- Indexes:
Company
-
job.model.ts
- Indexes:
{ tenantId: 1, slug: 1 }unique,{ tenantId: 1, active: 1 },{ tenantId: 1, location: 1 }
- Indexes:
-
staff.model.ts
- Indexes:
{ tenantId: 1, slug: 1 }unique,{ tenantId: 1, active: 1 }
- Indexes:
-
partner.model.ts
- Indexes:
{ tenantId: 1, slug: 1 }unique,{ tenantId: 1, active: 1 }
- Indexes:
Configuration
-
address.model.ts
- Indexes:
{ tenantId: 1, userId: 1 },{ tenantId: 1, isDefault: 1 }
- Indexes:
-
redirect.model.ts
- Indexes:
{ tenantId: 1, from: 1 }unique,{ tenantId: 1, active: 1 }
- Indexes:
-
user.model.ts
- Indexes:
{ tenantId: 1, email: 1 }unique,{ tenantId: 1, active: 1 }
- Indexes:
-
poll.model.ts
- Indexes:
{ tenantId: 1, slug: 1 }unique,{ tenantId: 1, active: 1 }
- Indexes:
-
booklet.model.ts
- Indexes:
{ tenantId: 1, slug: 1 }unique,{ tenantId: 1, active: 1 }
- Indexes:
-
faq.model.ts
- Indexes:
{ tenantId: 1, category: 1 },{ tenantId: 1, active: 1 },{ tenantId: 1, order: 1 }
- Indexes:
Interface Updates Required
Each interface must also be updated to include tenantId:
export interface IModel {
_id: string | Types.ObjectId;
tenantId: Types.ObjectId; // ADD THIS LINE
// ... rest of fields
}
Controller Updates Required
Pattern for All Controllers
import { Request, Response } from "express";
// tenantId is injected by tenantIsolation middleware
const tenantId = req.tenantId;
// All queries MUST include tenantId
const items = await Model.find({ tenantId, ...filters });
const item = await Model.findOne({ tenantId, _id: id });
const newItem = await Model.create({ tenantId, ...data });
const updated = await Model.updateOne({ tenantId, _id: id }, update);
const deleted = await Model.deleteOne({ tenantId, _id: id });
Controllers Requiring Updates (All Business Controllers)
- page.controller.ts
- product.controller.ts
- command.controller.ts
- news.controller.ts
- gallery.controller.ts
- category.controller.ts
- review.controller.ts
- room.controller.ts
- event.controller.ts
- vehicle.controller.ts
- contact.controller.ts
- newsletter.controller.ts
- user.controller.ts
- ... and all others
Migration Steps
1. Complete Model Updates (Current Phase)
# Review each model file and apply the pattern above
# Update 31 remaining models
2. Run Data Migration
cd BaldrTs
npm run migrate:tenant-isolation
3. Update Interfaces
# Add tenantId to all business interfaces
# Located in: BaldrTs/src/interfaces/
4. Update Controllers
# Add tenantId scope to all queries
# Located in: BaldrTs/src/controllers/
5. Test Each Module
- Create test tenant
- Test CRUD operations
- Verify tenant isolation
- Check performance with indexes
Important Notes
Unique Constraints
- BEFORE:
fieldName: { unique: true } - AFTER: Remove field-level unique, use compound index:
ModelSchema.index({ tenantId: 1, fieldName: 1 }, { unique: true });
Singleton Models (e.g., websiteManagement)
// Ensure only one document per tenant
ModelSchema.index({ tenantId: 1 }, { unique: true });
Performance Considerations
- ALL compound indexes should start with
tenantId: 1 - This enables MongoDB to efficiently filter by tenant first
- Critical for multi-tenant query performance
Testing Checklist
For each updated model:
- tenantId field added
- Compound indexes created with tenantId first
- Unique constraints updated to be tenant-scoped
- Interface updated with tenantId
- Controller queries scoped by tenantId
- CRUD operations tested
- Tenant isolation verified
Current Progress
- Models Updated: 5/36 (14%)
- Interfaces Updated: 1/36 (3%)
- Controllers Updated: 1/36 (3%)
- Migration Script: ✅ Ready
- Documentation: ✅ Complete
Next Steps
- Continue updating remaining 31 models
- Update all interfaces
- Run data migration
- Update controllers
- Test thoroughly