Personal project
Nexus SaaS
A multi-tenant business platform: one codebase serving many organisations, each with its own subdomain, features, roles and billing.
- Role
- Sole engineer
- When
- Nov 2025 – Mar 2026
- Status
- In progress
- Stack
- Next.js · TypeScript · PostgreSQL · Prisma · NextAuth · Stripe
Overview
Nexus is a business-management platform for agencies and small companies that want separate, branded workspaces from a single deployment. Each tenant gets a subdomain or a custom domain. Each tenant also gets its own set of enabled modules, roles and permissions, and a subscription.
The modules are deliberately broad. The interesting work is the layer underneath them that every module relies on.
The platform layer
- Tenant resolution. The request host is normalised, then matched to a tenant by subdomain or by a mapped custom domain.
- Shared schema. Tenant-owned tables carry a tenant id, and queries are scoped to it.
- Permissions. Around 40 seeded permission keys such as
finance.invoice.create, attached to roles per tenant and checked in server actions. - Feature flags. Per-tenant flags decide which modules a tenant has, and the navigation follows them.
- Billing. Stripe Checkout and subscriptions. Webhooks are verified, deduplicated and update the tenant's plan.
- Operations. A super-admin panel for provisioning tenants, toggling features and reviewing usage, plus an audit log for sensitive changes.
Engineering decisions
Idempotent webhook handling
- Why
- Stripe retries webhooks, and a retried
checkout.session.completedmust not provision twice. Each event is claimed by id in aWebhookEventrow before it's handled. A duplicate returns 200 so Stripe stops retrying. A failure releases the claim so the retry can run.
One guard for super-admin actions
- Why
- Admin checks used to be mixed across actions. I replaced them with a single super-admin guard and applied it to every cross-tenant action, including usage reporting, which had been missing a check.
Tenant deletion in a transaction
- Why
- Deleting a tenant removes its dependent records in one transaction, so a failure part-way can't leave orphaned data behind.
Known gaps
Nexus isn't finished, and the backlog says so:
- Many module actions check tenant membership but not the specific permission. Enforcing per-action permissions everywhere is the top item, ahead of any new feature.
- Read and write permission semantics need tightening, so that every mutation maps to one stable key and the UI hides exactly what the backend forbids.