Overview
Juno is a multi-tenant platform — every organization (tenant) shares the same codebase and infrastructure, but their data is completely isolated. This is enforced at the database layer using the mongoTenant plugin.
How it works
1. JWT contains tenant ID
Every authenticated request carries a JWT with a tid (tenant ID) claim. The auth middleware extracts this and attaches it to the request:
req.tid = tokenPayload.tid;
req.role = tokenPayload.role;
2. DI container auto-scopes models
The containerInitializer creates a per-request DI container. The junoModelFactory calls model.byTenant(tid) to scope every Mongoose model to the current tenant:
// junoModelFactory.ts
const scopedModel = model.byTenant(req.tid);
container.bind("ModelName").toConstantValue(scopedModel);
3. Services get pre-scoped models
Services receive already-scoped models via dependency injection. No manual tid filtering needed:
@injectable()
class GoalsService {
constructor(@inject("GoalModel") private goalModel: Model<IGoal>) {}
async list() {
// This ONLY returns goals for the current tenant
return this.goalModel.find({});
}
}
Key rules
Every Mongoose schema MUST have the mongoTenant plugin. A schema without it will leak data across tenants.
import mongoTenant from "mongo-tenant";
const GoalSchema = new Schema({ ... });
GoalSchema.plugin(mongoTenant, mongoTenantConfig);
Do’s and Don’ts
| Do | Don’t |
|---|
| Use DI-injected models (auto-scoped) | Query with raw mongoose.model() |
Trust model.find({}) is tenant-scoped | Add manual { tid } filters to queries |
Use model.byTenant(tid) in factories | Forget mongoTenant plugin on new schemas |
Cross-tenant access (rare)
For cron jobs or system-level operations that need all tenants:
const allTenantsModel = model.allTenants;
This bypasses tenant scoping. Use only in cron jobs, notifications, and admin tools.
Key files
| File | Purpose |
|---|
junoModelFactory.ts | Creates tenant-scoped models via DI |
containerInitializer.ts | Per-request DI container setup |
UserDataContext.ts | Extracts tid, role from request |
Testing
In tests, use byTenant(testTid) to scope models to a test tenant. Never test without tenant isolation.