Skip to main content

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

DoDon’t
Use DI-injected models (auto-scoped)Query with raw mongoose.model()
Trust model.find({}) is tenant-scopedAdd manual { tid } filters to queries
Use model.byTenant(tid) in factoriesForget 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

FilePurpose
junoModelFactory.tsCreates tenant-scoped models via DI
containerInitializer.tsPer-request DI container setup
UserDataContext.tsExtracts tid, role from request

Testing

In tests, use byTenant(testTid) to scope models to a test tenant. Never test without tenant isolation.
Last modified on April 15, 2026