Docs
A Turborepo monorepo, not a single Next.js app with an API folder bolted on. The backend and frontend are separate deployables that happen to share types and tooling.
The backend runs NestJS 11 on the Fastify 5 adapter. If you’ve worked with NestJS before, this is the detail most likely to trip you up — guards, interceptors, and request handlers all see a FastifyRequest/FastifyReply, not Express’s. Copy-pasting a NestJS snippet written against the default Express adapter is a common source of subtle type errors.
Every top-level folder is a pnpm workspace package, orchestrated by Turborepo. Run commands from the repo root with pnpm --filter to target one workspace, or let Turborepo fan a command out across all of them.
| Path | What it is |
|---|---|
| apps/api | NestJS 11 backend, running on the Fastify 5 adapter (not Express) — port 12002. |
| apps/web | Next.js frontend — App Router, React, port 12003. |
| packages/config | The module system — modules.config.ts is the per-customer enable/disable toggle (edit it, then run node build-scripts/build-edition.js [STARTER|PRO|ENTERPRISE]); modules.catalog.ts is the reference metadata (name, description, tier, routes) each entry maps to. |
| packages/database | Prisma schema, migrations, and the seed script. The single source of truth for the data model. |
| packages/types | Shared TypeScript types imported by both apps/api and apps/web (including the RBAC roles/permissions), so a DTO change can’t silently drift out of sync with the frontend. |
| packages/ui | Shared shadcn/ui component set used across the app’s pages. |
| packages/utils | Shared crypto, JWT, and validation helpers — checked here first before a feature reinvents one. |
| features/* | One folder per feature module (17 of them — see the Modules reference). Each ships its own module, controller, service, DTOs, and tests. |
| Layer | Choice |
|---|---|
| Backend | NestJS 11 + Fastify 5 adapter — use FastifyRequest/FastifyReply types, not Express’s |
| Frontend | Next.js 16 + React 19 |
| Database | PostgreSQL 18, via Prisma 7 |
| Cache / queues / rate limiting | Valkey 8 (Redis-protocol-compatible) |
| State management | Zustand for cross-component/global state, Jotai for scoped component atoms |
| UI | shadcn/ui + Tailwind CSS v4 |
| Testing | Vitest — use vi.mock(), vi.fn(), vi.spyOn(), not Jest’s API |
| Monorepo tooling | Turborepo + pnpm workspaces |
| Payments | Stripe (global) + Razorpay (India) |
apps/web calls apps/api cross-origin over REST — there’s no same-origin proxy in front of it. See API Contract for the auth and CORS mechanics that implies.features/ are enabled or disabled through packages/config/src/modules.config.ts — every module ships in the source, but which ones are wired into a given edition’s build is a config toggle, not a code fork.packages/database is the only thing that talks to PostgreSQL directly — feature modules import it rather than opening their own connections.Several structural choices — Turborepo over a plain multi-repo, NestJS + Fastify over Express, Prisma over Drizzle, custom auth over a third-party auth provider — are written up as Architecture Decision Records and shipped with the source, including the alternatives considered and why they were rejected.