Mudassir
All Projects

Nexus SaaS

All-in-one multi-tenant business management platform for teams who need more than spreadsheets.

RoleSolo Full-Stack Architect
TimelineJan 2025 — Mar 2025
Stack
Next.js 16React 19TypeScriptPostgreSQLPrisma ORMNextAuth v5StripeResendTanStack QueryZustandTailwind CSS v4Shadcn UIRadix UIFramer MotionRechartsZodjsPDFDOMPurifyDocker

The Challenge

The core challenge was implementing true multi-tenancy without the cost and complexity of a database-per-tenant model. Instead, I designed a shared-database, shared-schema architecture with row-level tenant isolation enforced at the query layer via Prisma. Every tenant-scoped model carries a tenantId foreign key, and all server actions resolve the current tenant before any data operation, making cross-tenant data leakage structurally impossible. The second major challenge was the authorization model. Building a 50+ permission RBAC system with custom role definitions, per-tenant feature flags, and middleware-enforced route protection required careful schema design and a centralized permission-key convention using module.entity.action dot notation. Webhook reliability was the third tricky area. Rather than relying on idempotency at the Stripe level, I introduced a WebhookEvent deduplication table that records every processed event ID before any business logic runs, turning an at-least-once delivery guarantee into an exactly-once processing guarantee.

Architectural Decisions

hub

Shared-schema multi-tenancy with Prisma row-level isolation

Chose shared database over database-per-tenant to avoid infrastructure overhead and migration complexity. All 20+ tenant-scoped models carry a tenantId foreign key, and a centralized tenant resolution utility injects the tenant context into every query.

bolt

Next.js Server Actions as the primary data mutation layer

Instead of building a REST or tRPC API layer, all client-to-server mutations use Next.js 16 Server Actions. Each action calls requireTenantPermission() before touching the database, making it impossible to bypass auth by hitting an unprotected endpoint.

verified

Webhook idempotency via WebhookEvent deduplication table

Stripe webhooks deliver events at-least-once. To prevent double-processing billing events, every incoming webhook is first checked against a WebhookEvent table keyed on the Stripe event ID. Only new, unseen events are processed.

admin_panel_settings

Granular RBAC with custom role definitions

Designed a three-tier authorization model: super-admin, tenant-admin, and tenant-user with RBAC via 50+ permissions in module.entity.action format. Tenants can define custom roles with allowlists of features and permissions.

toggle_on

Per-tenant feature flags for module gating

Each tenant has a TenantFeature join table that enables or disables modules independently. This decouples billing logic from feature access and keeps the tenant-facing UI clean by hiding disabled modules entirely.

dns

Subdomain-based tenant routing at the middleware layer

Tenants are resolved by extracting the subdomain from the incoming request hostname inside Next.js middleware, before any React rendering occurs. The resolved tenant is passed into the request context without prop drilling.

Impact

6
business modules in one platform
50+
granular RBAC permissions
34+
database models with tenant isolation
tenants from a single deployment
Next Project

MacSweep