Jumentix
Modular SaaS blueprint

Launch one deployable, preserve every domain boundary

Ship faster as a modular monolith with multi-tenancy, RBAC, organizations, users, and event-driven collaboration already modeled.

Jumentix open-source mascot

What this blueprint gives your team

  • Lower first-release operating cost
  • Feature-driven modules keep changes local
  • Message contracts support later extraction
bun install
bun run cli
bun run dev:express

From zero to first MVP

A practical launch sequence for this blueprint

Use this path when the first MVP is a complete SaaS slice: sign in, tenant or organization scope, one core workflow, and one deployable.

Define the tenant-owned workflow

Choose the first organization-owned capability, such as categorized tasks, and define who can create, view, and complete it.

  • Define TenantContext with organizationId and userId on every request.
  • Model Task with organizationId so every record belongs to a tenant.
  • Write the TenantPolicy rule: a tenant can only write its own records.
  • Write acceptance criteria: cross-tenant writes are denied with an explicit error.

MVP output: one bounded context with tenant and RBAC expectations.

type TenantContext = { organizationId: string; userId: string };
type Task = { id: string; organizationId: string; title: string; categoryId: string };

class TenantPolicy {
  canWriteTask(context: TenantContext, organizationId: string) {
    return context.organizationId === organizationId;
  }
}

Definition of done for the first MVP

  • A cross-tenant read or write fails in an automated test.
  • The first feature changes stay inside its module boundary.
  • One supervised process runs the tenant workflow end to end.
  • Docs, routes, and module boundaries agree in the release evidence.

Deliberately out of scope

  • Billing, plans, and subscription lifecycle.
  • SSO and external identity providers.
  • Full audit log and fine-grained permission matrix.
  • Module extraction into separate services.

MVP scope

Keep the first release small enough to prove

One tenant workflow

Organization, user, Category and Task prove tenancy plus real feature behavior.

Product slice

One deployable

Keep operations simple while preserving feature modules and composition boundaries.

Delivery slice

One role policy

Prove superadmin/admin/user behavior before adding a full permission matrix.

Security slice
Proof areaQuestion to answerJumentix mechanismMVP evidence
Tenant safetyCan one organization see another organization data?Tenant-aware use-case tests and policy checks.Cross-tenant reads and writes fail.
Module boundaryCan the first feature change without touching unrelated modules?Feature folder and workspace boundary checks.Changes stay local to the bounded context.
Pilot readinessCan the whole MVP run as one supervised app?Dev/staging profile, smoke test, docs and prepublish evidence.One deployable is ready for first users.

Practical implementation

Complete code for the first working slice

These examples keep Category and Task as the product vocabulary and show the controller, contract, client, worker, or state layer needed to reach a runnable MVP.

type TenantContext = { organizationId: string; userId: string };
type Task = { id: string; organizationId: string; title: string; categoryId: string };

class TenantPolicy {
  canWriteTask(context: TenantContext, organizationId: string) {
    return context.organizationId === organizationId;
  }
}

class TaskModule {
  private readonly tasks = new Map<string, Task>();

  constructor(private readonly tenantPolicy: TenantPolicy) {}

  create(context: TenantContext, input: { title: string; categoryId: string }) {
    if (!this.tenantPolicy.canWriteTask(context, context.organizationId)) {
      return { ok: false, error: 'tenant access denied' };
    }
    const task: Task = {
      id: crypto.randomUUID(),
      organizationId: context.organizationId,
      title: input.title,
      categoryId: input.categoryId
    };
    this.tasks.set(task.id, task);
    return { ok: true, result: task };
  }
}

const taskModule = new TaskModule(new TenantPolicy());
export const modules = { taskModule };

Build the product. Keep the architecture.

Explore the source, run the factory locally, and turn your next Node.js service into a repeatable platform capability.