Case study · № 102026~3.5 months

RMC ERP

A single-codebase ERP for ready-mixed-concrete plants — order-to-cash, procure-to-pay, batch production, IS-standard lab testing, real-time dispatch, and GST-compliant accounting. 116 data models, built end-to-end, solo.

Build~3.5 mo
RoleSolo
Data models116
StackNestJS · Next.js
rmc · hero
ClientAPIModulesDataNext.jsAnt Design 6NestJSREST · JWT · RBACRediscache · sessionsSocket.io/dispatchSales / Purchaseorder-to-cashLabIS-standard testsAccountingGST · ledgersPostgreSQLPrisma · 116 modelsPuppeteerPDF invoicescacheinvoices
Fig. 1 · Single-codebase ERP — NestJS modules over a 116-model Prisma·Postgres schema, Redis-cached RBAC, a Socket.io dispatch feed, and Puppeteer invoices.
01

Context

A ready-mixed-concrete plant is a surprising amount of software hiding behind a batching drum. Sales cut quotations, the plant floor runs mix designs and batches, a lab certifies cube strength, vehicles dispatch challans, and accounts reconcile it all against GST — and in most plants those live in five disconnected tools, or a stack of paper registers.

RMC is the attempt to fold all of that into one system, so a job can be traced from a customer inquiry through production, lab certification, delivery challan, e-invoice, and payment against a single ledger. Built like a product, not an internal script.

rmc · dashboard
02

Architecture

One Turborepo monorepo, pnpm workspaces. A NestJS 11 API (apps/api), a Next.js 16 App-Router web client (apps/web), and shared packages — a generated Prisma client, Zod-backed shared types, and finance/date utilities — so a validation shape is written once and enforced on both sides.

The API is modular: roughly two-dozen feature modules (auth, masters, accounting, inventory, sales, purchase, production, lab, vehicle, dispatch, reports). Auth is hand-rolled — short-lived JWT access tokens with rotating refresh tokens, argon2 hashing, refresh sessions in Postgres and Redis, and an access-token blacklist in Redis on logout.

monorepo · api modules
03

The data model

The heart of it is the schema — 116 Prisma models across fifteen domain files, no enums faked as loose strings where a relationship was the honest answer. RBAC is module.action permissions resolved through a guard and cached per-user in Redis, with multi-company / multi-branch tenancy carried on the records themselves.

Dispatch runs live: a Socket.io gateway on a /dispatch namespace pushes status changes, new schedules, and challan updates to the floor without a refresh. Invoices, registers, and lab certificates render to PDF through Puppeteer; lists export to Excel.

116 models · prisma
04

The domain is the hard part

The interesting complexity wasn’t the CRUD — it was encoding the domain honestly. IS 383 sieve analysis and 7/28-day cube-strength tracking in the lab module. GST sales invoices with credit and debit notes, TDS, and GSTR-shaped accounting. Mix-design approval flows before a grade can be produced. Getting those right is what separates a demo from something a plant would actually run its money through.

05

Decisions & tradeoffs

  • Hand-rolled auth over a hosted provider. An ERP needs a real permission matrix and audit trail, not a magic-link UX. Rotation and Redis blacklisting were worth owning.
  • Shared types package, not two schemas. Zod shapes in a shared package meant the API and the Ant Design forms never drifted — the tax on a monorepo paid for itself by week two.
  • Strings over Prisma enums for status. A deliberate convention — migrations stay cheap when a new status is a value, not a schema change. The cost is discipline at the boundary.
06

State & what I would change

It runs the full order-to-cash and procure-to-pay cycle today, seeded against a realistic plant. If I were starting over I’d pull reporting into its own read-optimised path earlier — Puppeteer PDF generation in the request cycle is fine until a month-end register is 400 pages. And a job queue for the email worker from day one, rather than the cron I bolted on.

Built with
NestJS 11Next.js 16React 19TypeScriptPostgreSQL 17Prisma 7RedisAnt Design 6Socket.ioTurborepoPuppeteerDocker
← PreviousCafe POSNext →SwasthyaSetu
You found the trick.
Thanks for
looking closely.
↑ ↑ ↓ ↓ ← → ← → B A · konami