feat: add multi-tenant SaaS template (--multi-tenant) - #133
Merged
Conversation
Extends setup-project.sh with a --multi-tenant flag (composable with any platform target) that scaffolds tenant isolation: - src/tenancy/ modules: strategy config (TENANCY_STRATEGY=row|schema), tenants registry schema + tenantScopedColumns() helper, tenant resolution middleware (JWT claim, subdomain, X-Tenant-ID header), tenant-scoped query facade + SET LOCAL app.tenant_id helper (row strategy), and per-tenant schema + search_path helpers (schema strategy) - Row Level Security policy template (fail-closed FOR ALL policy per tenant-scoped table), shipped as src/db/rls-policies.sql to apply via a custom Drizzle migration - Tenant-isolation test suite: resolution + SQL scoping tests run everywhere; an opt-in live PostgreSQL suite (TENANCY_TEST_DATABASE_URL) proves RLS visibility, WITH CHECK enforcement, and search_path isolation as a non-superuser role - docs/api-development/multi-tenancy.md documenting the trade-offs between row-based and schema-based isolation and usage of both - CI: validate templates/multi-tenant and add a node-multi-tenant generated-project job that also runs the tenancy tests Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds a multi-tenant SaaS template that extends the standard project setup with tenant isolation patterns. Generate with:
./scripts/setup-project.sh my-saas --node --multi-tenant # composes with any platform flagIsolation strategies (selectable via
TENANCY_STRATEGY)row(default): shared tables with atenant_idFK on every resource table, a tenant-scoped query facade (createTenantDb) that injects the tenant predicate into every query, and PostgreSQL Row Level Security as a fail-closed backstop (withTenant()bindsapp.tenant_idviaSET LOCAL; the policy template ships atsrc/db/rls-policies.sql).schema: one PostgreSQL schema per tenant, switched with transaction-scopedsearch_path(withTenantSchema()), plus schema creation for onboarding and a documented per-tenant migration runner.Tenant resolution middleware
tenantResolverresolves the tenant per request from, in configurable priority order:tenant_id(signed — default first)acme.api.example.com→ slugacme)X-Tenant-IDheader (documented as spoofable / gateway-only)Unresolvable →
400; unknown tenant →404(prevents tenant enumeration).Schema additions
tenantstable (id, name, slug, plan, created_at), atenantScopedColumns()helper for resource tables, and an exampleprojectstable with thetenant_idindex. The generateddrizzle.config.tsincludes the tenancy schema sodrizzle-kit generatepicks it up.Tests
api/tests/tenancy.test.ts: resolution and SQL-scoping tests run with no database; settingTENANCY_TEST_DATABASE_URLactivates a live suite that proves RLS visibility per tenant, fail-closed behavior without tenant context,WITH CHECKblocking cross-tenant writes/updates/deletes, andsearch_pathschema isolation — running as a non-superuser role, in a throwaway namespace cleaned up afterwards.Docs
docs/api-development/multi-tenancy.mddocuments the trade-offs between the two strategies (tenant count, migrations, cross-tenant analytics, backup/restore, noisy neighbors, compliance optics), when to pick which, and pooling/SET LOCALcaveats. Linked from the standards README, root README, and CLAUDE.md.CI
templates/multi-tenantadded to template validationnode-multi-tenantgenerated-projects matrix entry that generates with the flag, type-checks, and runs the tenant-isolation testsTest plan
bash -n+ shellcheck (severity=warning) clean onsetup-project.sh--dry-runpreviews the tenancy files/appends correctly--multi-tenantproject:pnpm typecheckpasses, 51 tests pass (live suite skipped without DB)ci.ymlYAML validatesNotes
While verifying, found a pre-existing issue (not introduced or fixed here):
setup-project.shfails on pnpm 11 for every platform flag withERR_PNPM_IGNORED_BUILDS(esbuild build scripts). CI is unaffected (repo pinspnpm@10.15.1). Worth a follow-up to pre-approve builds in generated projects.🤖 Generated with Claude Code