← all work
CS-04Live · 700+ students

School ERP

A school runs its day on something I built by myself.

Role
Built alone, end to end
Timeline
2026 · 10 weeks
Stack
NestJSPrisma + PostgreSQLNext.jsFlutterBullMQRazorpayFCMSQLCipherTurborepo

What it is

AVG Vidyalaya is a matriculation school in Chennai with more than 700 students. Their ERP is something I built alone: management portal, teacher portal and super-admin portal served from one Next.js app by host-based routing, a Flutter app for parents, and one NestJS API behind all of it. It covers admissions, attendance, marks, fees, payroll, homework, messaging and certificates. It is multi-tenant by design, so the next school is added without a second deployment.

Permissions are not role strings. There is a catalog of 130 capabilities, each tri-state, with dependency rules and pairing keys that force the person who initiates a sensitive action and the person who approves it to be different people. Schools compose roles from the catalog; they never invent capabilities.

Fees is a full finance module: cashier collection, concessions, waivers, installments, late fees, cheque clearance, Razorpay payment from the parent's phone, then settlement and bank reconciliation with discrepancy reporting. Every receipt PDF carries a signed QR code that anyone can verify at a public URL.

Architecture

surfaces
Management portalTeacher portalSuper-admin portalFlutter parent app
api
NestJS · 225 endpoints · 130-capability RBAC
sync
Domain events → BullMQ3-level idempotency + storm collapseDPDPA filter on parent reads
data
Postgres 16 · 148 Prisma modelsRedis · optional by designMinIO / S3
Four surfaces on one API. Parent reads never touch source tables.

Decisions that mattered

01

A sync layer instead of WebSockets

Parent-facing reads never touch the school's source tables. Modules emit domain events onto a queue; a sync service builds what parents see, filtered for DPDPA compliance and cached per parent. Idempotency is enforced at three levels, and a storm-collapse window stops one bulk marks entry from firing 700 notifications.

02

Everything degrades when Redis is absent

Redis is optional by configuration. Queues fall back to inline fan-out, caches to direct queries, scheduled jobs carry catch-up sweeps for anything missed at boot. A school's ERP should limp, not die, when a box goes down.

03

Receipts that prove themselves

Every fee receipt is a PDF with a cryptographically signed QR code. Scan it and a public verification page confirms the receipt is genuine, no login needed. Parents trust paper; this makes the paper trustworthy.

04

A parent app built like a bank app

The Flutter app keeps an encrypted offline cache under SQLCipher with the key in secure storage, stacks sessions for parents with several children, and offers biometric lock. Feature gates are server-driven so unfinished tabs ship dark. Fonts include Noto Sans Tamil, because the audience reads Tamil.

05

Spec first, at a strange ratio

45 functional spec documents, about 47,600 lines of markdown, drive the build through a gated rollout chain. That is roughly one line of specification for every four lines of code. It is why 184k lines in ten weeks did not collapse into a mess.

By the numbers

~184,000
lines in about ten weeks
700+
students on it daily
148
Prisma models
225
API endpoints
130
RBAC capabilities
4
surfaces: 3 portals + parent app
45
functional spec documents
212
test files across API, web, mobile