← Back to work
Case study / 03

School ERP SaaS

School operations built around the way Indian schools work.

Team
Me, myself & a few AI agents
Audience
Schools with 200–600 students
Progress
Beta testing

The Problem

Walk into most private schools in India with 200 to 600 students, and you will find the same scene: fee collection tracked in physical registers, attendance marked on paper and transcribed to Excel at the end of the day, and parent communication happening through a patchwork of WhatsApp groups managed by individual teachers. The schools are not poorly run; they just never had software built for them.

Enterprise ERP systems exist for large schools, but they are expensive, complex, and assume the presence of dedicated IT staff. The smaller school segment (which represents the majority of private schools in India) has been largely ignored. They cannot afford ₹50,000/year per school, they cannot train non-technical staff on complex interfaces, and they need a system that fits how they actually communicate with parents: through WhatsApp.

This is what the School ERP SaaS was built to solve. A product designed specifically for CBSE private schools and Maharashtra and MP state board schools, priced at approximately ₹10,000 per school per year, with WhatsApp as a first-class communication channel throughout the entire system.

What Was Built

The product is a multi-tenant SaaS platform: one codebase and one infrastructure serving many schools, each completely isolated from the others. Every piece of school data lives behind a schoolId, sourced exclusively from the JWT token on every request. No school can access another school's data, and this is enforced at the middleware level, not just the query level.

Six roles were designed into the system: super admin (platform-wide), admin (multi-school access), principal, accountant, teacher, and worker. Each role has a specific set of permissions defined in a static permission catalog. Every protected route is guarded by a requirePermission middleware that checks the token's role against the permission required, requirePermission('finances.fees.collect'), for example. The system was designed so that a future custom role builder would only need to change the data source for permissions, not the middleware itself.

The Technical Foundation

PostgreSQL was chosen over Firebase or MongoDB after careful consideration. Student data is deeply relational: a student has admissions records, fee payment history, attendance records, guardian contacts, class assignments, and document uploads, all linked together. A document database would have required significant denormalization and made queries that span multiple data types complex and fragile. PostgreSQL with Prisma gave proper foreign keys, transactions, and the ability to write clean queries without workarounds.

Custom JWT authentication was built rather than using an auth service. The reason was architectural: every API route is scoped to a schoolId that must come only from the server-issued token, never from the request body. The choice kept school scoping within the application’s authentication flow and avoided introducing a separate service and synchronization layer.

WhatsApp was chosen as the primary parent communication channel because it is where Indian school parents actually are. SMS is expensive and largely ignored. Email is rarely checked by parents in this school segment. WhatsApp penetration in Indian school communities is near-universal, and parents are already conditioned to receive school updates there. A two-tier architecture was built: a base tier using a shared platform number for one-way confirmations like fee receipts and attendance alerts, and a premium tier with a dedicated per-school number for push notifications and two-way communication. The integration was built through Meta Cloud API directly, using a provider-agnostic adapter interface so the underlying provider can be swapped without changing application logic.

BullMQ with Redis handles all asynchronous work: WhatsApp message delivery, email notifications, Excel imports, and database backups. Five named queues ensure different job types are processed with appropriate priority and retry behavior. Nothing that could take time blocks the request/response cycle.

The Product Decisions That Mattered

Two product controls were built in by design. First, Aadhaar-based student deduplication was implemented at admission. Each student is identified by their Aadhaar number, so their history follows them when they transfer to another school on the platform, and duplicates are prevented at the database level.

Second, changes to a student’s fee structure require two-person approval: the accountant makes the change, and a principal or admin confirms it. This is enforced by the system.

Excel import was built with per-row error reporting because schools migrating from spreadsheets will always have messy data. Rejecting an entire import because three rows have formatting issues is unusable in practice. Each row is validated independently, errors are reported with row numbers and specific messages, and valid rows are imported while invalid ones are returned for correction.

The Outcome

The platform is in beta testing. It has multi-tenant architecture, six working roles, student admissions with Aadhaar deduplication, attendance tracking with WhatsApp alerts per student, fee collection with WhatsApp receipts, two-person approval for student fee-structure changes, Excel import with error reporting, automated database backups to Cloudflare R2, a Super Admin panel for platform management, and full WhatsApp Business API integration running on Meta's Cloud API.

The first school onboarding is planned immediately after deployment. The architecture is built to scale: adding a new school is a matter of onboarding, not configuration.

Stack

  • Next.js (App Router)
  • TypeScript
  • Tailwind CSS
  • Node.js
  • Express 5
  • PostgreSQL
  • Prisma 7
  • BullMQ
  • Redis
  • Cloudflare R2
  • ZeptoMail
  • WhatsApp Business API (Meta Cloud API)
  • JWT
  • Playwright
  • pnpm monorepo
  • Railway
  • Docker