RAMSIO

Docs

Architecture

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.

Why NestJS + Fastify, not Express

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.

Workspace layout

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.

PathWhat it is
apps/apiNestJS 11 backend, running on the Fastify 5 adapter (not Express) — port 12002.
apps/webNext.js frontend — App Router, React, port 12003.
packages/configThe 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/databasePrisma schema, migrations, and the seed script. The single source of truth for the data model.
packages/typesShared 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/uiShared shadcn/ui component set used across the app’s pages.
packages/utilsShared 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.

Tech stack

LayerChoice
BackendNestJS 11 + Fastify 5 adapter — use FastifyRequest/FastifyReply types, not Express’s
FrontendNext.js 16 + React 19
DatabasePostgreSQL 18, via Prisma 7
Cache / queues / rate limitingValkey 8 (Redis-protocol-compatible)
State managementZustand for cross-component/global state, Jotai for scoped component atoms
UIshadcn/ui + Tailwind CSS v4
TestingVitest — use vi.mock(), vi.fn(), vi.spyOn(), not Jest’s API
Monorepo toolingTurborepo + pnpm workspaces
PaymentsStripe (global) + Razorpay (India)

How the pieces talk to each other

  • 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.
  • Feature modules under 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.
  • Prisma’s generated client in packages/database is the only thing that talks to PostgreSQL directly — feature modules import it rather than opening their own connections.

Architecture decisions

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.