Multi-Tenant Architecture Implementation
🎯 Overview
This implementation provides a complete multi-tenant architecture for the Baldr platform, allowing a single codebase, API, and database to serve thousands of isolated clients (tenants).
📋 What Has Been Implemented
✅ Backend (API)
-
Tenant Model (
BaldrTs/src/models/tenant.model.ts)- Complete tenant management
- Status tracking (active, suspended, inactive)
- Feature/module assignment
- Metadata support
-
Updated Credential Model (
BaldrTs/src/models/credential.model.ts)- Added
tenantIdfield for client association - Added
isInleedflag to distinguish internal admins from clients - Created compound indexes for performance
- Added
-
Authentication Updates (
BaldrTs/src/services/core/credential.service.ts)- Login now returns tenant information
- User creation supports tenant assignment
- Token includes tenant context
-
Tenant Isolation Middleware (
BaldrTs/src/middlewares/tenantIsolation.middleware.ts)- Automatically extracts tenant context
- Inleed users can override tenant via query/header
- Client users always scoped to their tenant
- Three variants:
tenantIsolation,requireTenant,optionalTenant
-
Tenant Management Endpoints (
BaldrTs/src/controllers/tenant.controller.ts)POST /api/tenant- Create tenant (Inleed only)GET /api/tenant- List all tenants with pagination (Inleed only)GET /api/tenant/:id- Get tenant detailsPUT /api/tenant/:id- Update tenant (Inleed only)DELETE /api/tenant/:id- Delete tenant (Inleed only)GET /api/tenant/:id/stats- Get tenant statistics
-
Migration Script (
BaldrTs/src/scripts/migrate-to-multitenant.ts)- Creates default tenant
- Assigns tenantId to existing credentials
- Sets isInleed flag based on role
- Idempotent and safe to run multiple times
✅ Frontend (Back Office)
-
Tenant Context (
Baldr-Bo/app/context/tenant.context.tsx)- Manages selected tenant state
- Persists selection in localStorage
- Provides methods to switch tenants
-
Updated User Context (
Baldr-Bo/app/context/user.context.tsx)- Handles dual authentication flow
- Inleed users → Tenant selection page
- Client users → Direct to dashboard
-
Tenant Selection Page (
Baldr-Bo/app/pages/tenants/tenantSelection.page.tsx)- Paginated tenant list
- Search functionality (debounced)
- Status indicators
- Click to select tenant
-
Tenant API (
Baldr-Bo/app/api/tenant.api.ts)- Complete tenant management client
- Handles all CRUD operations
- Includes statistics fetching
-
Updated Interfaces
ICredentialnow includestenantIdandisInleed- New
ITenantinterface ITenantListResponsefor pagination
📚 Documentation
-
API Migration Guide (
BaldrTs/documents/MULTI_TENANT_MIGRATION.md)- Complete implementation details
- Step-by-step model migration guide
- Security considerations
- Index requirements
-
Back Office Guide (
Baldr-Bo/docs/MULTI_TENANT_BACKOFFICE.md)- User flow diagrams
- Component documentation
- API call patterns
- Testing scenarios
🚀 Quick Start
1. Run Database Migration
cd BaldrTs
npm run migrate:multitenant
# or
npx ts-node src/scripts/migrate-to-multitenant.ts
This will:
- Create a default tenant
- Assign all existing users to the default tenant
- Set
isInleedflags based on user roles
2. Configure Default Tenant
Edit BaldrTs/src/scripts/migrate-to-multitenant.ts:
const MIGRATION_CONFIG: MigrationConfig = {
defaultTenant: {
name: "Your Company Name",
slug: "your-company",
contactEmail: "admin@yourcompany.com",
status: TenantStatus.ACTIVE,
},
inleedRoles: ["inleedMaster", "inleedSlave"],
};
3. Test the Implementation
Create a Test Tenant (as Inleed user)
curl -X POST http://localhost:3000/api/tenant \
-H "Authorization: Bearer YOUR_INLEED_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Test Client",
"slug": "test-client",
"contactEmail": "test@client.com",
"status": "active"
}'
Create a User for the Tenant
curl -X POST http://localhost:3000/api/credential \
-H "Authorization: Bearer YOUR_INLEED_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"userName": "testuser",
"email": "user@test-client.com",
"password": "SecurePass123!",
"firstName": "Test",
"lastName": "User",
"role": "commonMaster",
"tenantId": "TENANT_ID_FROM_PREVIOUS_STEP",
"isInleed": false
}'
Test Login Flows
Inleed User Login:
- Login → Should redirect to
/tenants/selection - Select a tenant
- Should redirect to
/dashboard - All data scoped to selected tenant
Client User Login:
- Login → Should redirect directly to
/dashboard - Cannot access tenant selection
- All data automatically scoped to their tenant
⚠️ What Still Needs to Be Done
High Priority
-
Update All Business Models
- Add
tenantIdfield to all business collections - Models that need updating:
command.model.tspage.model.tsproduct.model.tscategory.model.tsgallery.model.tsnews.model.tsnewsletter.model.ts- And all other business models
- Add
-
Update All Controllers
- Apply
tenantIsolationmiddleware to all protected routes - Update queries to include
tenantIdfilter - Validate tenant ownership before updates/deletes
- Apply
-
Update All Services
- Accept
tenantIdparameter in service methods - Scope all database queries by tenant
- Update create operations to include tenantId
- Accept
-
Data Migration for Other Collections
- Create migration scripts for each business collection
- Assign default tenant to existing records
- Verify data integrity
Medium Priority
-
Add Tenant Indicator to UI
- Show current tenant in header (for Inleed users)
- Add tenant switching button
- Display tenant info for client users
-
Update All React Components
- Check for tenant selection before data fetching
- Handle "no tenant selected" state
- Include tenant context in API calls
-
Add Route Guards
- Protect tenant-specific routes
- Ensure Inleed users have selected a tenant
- Redirect appropriately
-
Testing
- Unit tests for tenant isolation
- Integration tests for cross-tenant security
- E2E tests for both user flows
Low Priority
-
Analytics & Monitoring
- Track tenant usage
- Monitor cross-tenant access attempts
- Dashboard for tenant statistics
-
Additional Features
- Tenant switching history
- Recent tenants list
- Tenant search in header
- Bulk tenant operations
📖 Usage Examples
Backend: Securing a Route
import { authenticate } from '../middlewares/authentication.middleware';
import { tenantIsolation, requireTenant } from '../middlewares/tenantIsolation.middleware';
// Apply middleware chain
router.get(
'/products',
authenticate, // 1. Verify user authentication
tenantIsolation, // 2. Extract tenant context
requireTenant, // 3. Ensure tenant is present
productController.getAll
);
// In controller
async getAll(req: Request, res: Response) {
// req.tenantId is now available and verified
const products = await Product.find({
tenantId: req.tenantId,
active: true
});
res.json(products);
}
Frontend: Using Tenant Context
import { useUser } from "~/context/user.context";
import { useTenant } from "~/context/tenant.context";
function ProductList() {
const { user } = useUser();
const { currentTenant } = useTenant();
const [products, setProducts] = useState([]);
useEffect(() => {
// For Inleed users, ensure tenant is selected
if (user?.isInleed && !currentTenant) {
return;
}
// Fetch products (tenant context handled by API)
ProductApi.getAll().then(setProducts);
}, [user, currentTenant]);
if (user?.isInleed && !currentTenant) {
return <SelectTenantMessage />;
}
return <DataTable value={products} />;
}
🔒 Security Checklist
- ✅ Tenant isolation middleware created
- ✅ Authentication includes tenant info
- ✅ JWT tokens include tenant context
- ⚠️ All routes protected with middleware (needs implementation)
- ⚠️ All queries scoped by tenantId (needs implementation)
- ⚠️ Cross-tenant access prevented (needs testing)
- ⚠️ Inleed actions logged (needs implementation)
📊 Performance Considerations
Implemented
- ✅ Compound indexes on credentials (tenantId + other fields)
- ✅ Tenant list pagination
- ✅ Debounced search in tenant selection
- ✅ LocalStorage persistence for tenant selection
To Implement
- ⚠️ Compound indexes on all business collections
- ⚠️ Query optimization for tenant-scoped data
- ⚠️ Caching strategies per tenant
- ⚠️ Connection pooling per tenant (if needed)
🆘 Troubleshooting
"User not associated with any tenant"
Cause: Client user doesn't have tenantId Solution: Run migration script or manually assign tenant
"Tenant context required for this operation"
Cause: Inleed user hasn't selected a tenant Solution: Ensure user selects tenant before accessing protected routes
"Only Inleed users can create tenants"
Cause: Client user attempting tenant management
Solution: Verify user has isInleed: true flag
Redirect loop in Back Office
Cause: User context or tenant context not loading properly Solution: Clear localStorage and cookies, re-login
📞 Support
For questions or issues:
- Check documentation in
/documents/and/docs/ - Review the migration guides
- Contact the development team
🎓 Learning Resources
- API Architecture:
BaldrTs/documents/MULTI_TENANT_MIGRATION.md - Back Office:
Baldr-Bo/docs/MULTI_TENANT_BACKOFFICE.md - Tenant Model:
BaldrTs/src/models/tenant.model.ts - Tenant Context:
Baldr-Bo/app/context/tenant.context.tsx - Migration Script:
BaldrTs/src/scripts/migrate-to-multitenant.ts
🚧 Development Roadmap
Phase 1: Core Implementation ✅ COMPLETE
- Create tenant model
- Update credential model
- Implement tenant isolation middleware
- Create tenant management API
- Build tenant selection UI
- Update authentication flow
Phase 2: Model Migration ⚠️ IN PROGRESS
- Update all business models
- Add compound indexes
- Migrate existing data
- Update all controllers
Phase 3: Frontend Integration ⚠️ IN PROGRESS
- Update all components
- Add tenant indicators
- Implement route guards
- Comprehensive testing
Phase 4: Polish & Production 🔜 UPCOMING
- Performance optimization
- Security audit
- Documentation review
- Production deployment
Version: 1.0.0
Last Updated: January 2026
Status: Core implementation complete, model migration in progress