Products / SaaS / Backend Infrastructure
TenantScope
Multi-tenant Postgres: orgs, memberships, orgId-scoped queries
Users, organizations, memberships (owner/admin/member), invite stubs, JWT starter auth, and middleware that resolves tenant then scopes every Notes query by orgId.
Value
Problem. B2B SaaS needs organizations and memberships. Without a trusted tenant resolve + orgId filter, Customer A can see Customer B’s rows.
How it helps. Users, organizations, memberships (owner/admin/member), invite stubs, JWT starter auth, and middleware that resolves tenant then scopes every Notes query by orgId.
Why buy. Ship the multi-tenant membership spine most starters skip — without bolting on a full IdP or billing product.
- JWT + bcrypt starter auth (documented as not a production IdP)
- Tenant middleware: X-Tenant-Id / X-Tenant-Slug / subdomain stub + membership check
- Notes CRUD proving orgId scoping; Vitest rejects cross-tenant access
How it works
flowchart LR
User[User] -->|membership| Membership
Membership -->|role: owner/admin/member| Org[Organization]
Org --> Notes[Notes / resources]
Request[HTTP request] -->|JWT| Auth[requireAuth]
Auth -->|X-Tenant-Id / slug| Tenant[requireTenant]
Tenant -->|membership check| Scoped[orgId-scoped queries]
Scoped --> Notes
Mermaid flowchart (render in GitHub / VS Code / mermaid.live).
Use case
Alice creates Org A, Bob is in Org B; Alice’s JWT + X-Tenant-Id for Org A lists only Org A notes — Bob’s token cannot read Alice’s rows.
What you get
- Express API with auth, orgs, invites, notes routes
- Prisma models: User, Organization, Membership, Invite, Note
- docker-compose.yml (Postgres on host 5433)
- Vitest: slug, auth, cross-tenant rejection
- MIT license + Mermaid architecture
Project structure
tenantscope/
src/
middleware/ auth, tenant
routes/ auth, orgs, notes, health
services/ auth, orgs, notes
lib/ config, prisma, slug, logger
prisma/ schema + migrations
tests/ auth, tenant, slug
docker-compose.yml
Quick start
docker compose up -d
cp .env.example .env # JWT_SECRET ≥ 32 chars
npm install && npm run prisma:generate && npm run prisma:migrate
npm run dev
Example
From src/middleware/tenant.ts
// Resolve tenant from trusted header/slug, verify membership, then:
// always query with orgId from req.tenant — never from unchecked body fields.
const notes = await prisma.note.findMany({
where: { orgId: req.tenant.orgId },
});
Tested
17 Vitest tests — password hashing, register/login, slug helpers, and cross-tenant note access rejection.
npm test
2026-09-20 — 17 passed; npm run build OK
Design decisions
- Always filter by orgId from req.tenant after membership check — never trust body orgId alone
- Roles kept to owner/admin/member so RBAC stays readable
- Notes demo proves scoping end-to-end without inventing a full product domain
Limitations
- Not billing / Stripe subscriptions
- Not a React UI or full IdP (Okta/Auth0/Clerk)
- Starter JWT auth — harden secrets, rotation, and session policy before production
- Subdomain tenant resolve is a stub you wire to your DNS
Who it’s for
Indie founders building B2B multi-tenant APIs on Postgres + TypeScript.
Not for: Teams that need an all-in-one auth/billing platform or RLS-only Postgres policies out of the box.
What you own
Full MIT-licensed TypeScript source via Gumroad ZIP. Fork it, ship it commercially, keep the license notice. You are responsible for secrets, hardening, and production ops.
Related
Demo