Overview
I built the backend for a multi-tenant school management platform in .NET 10. Each school gets its own PostgreSQL schema and file folder, and the API covers 27 modules, from admissions, exams and payroll to accounting, a library, messaging and fingerprint attendance, with 484 routes in all.
Problem
School software is usually sold school by school: each school gets its own installation, its own database and its own upgrades. That is slow to set up, expensive to maintain and hard to keep consistent. Schools also need far more than a student list: fees, payroll, exams, attendance, a library and messages.
Approach
I built one API that serves many schools. A registry holds the list of schools, and every school gets its own database schema and file folder created automatically. Each request carries the school it belongs to, so the same code serves every school without ever mixing their data. The modules cover a school's whole day, and a documented module index kept the work organised as it grew.
Outcome
- API routes
- 484
- Controllers
- 82
- Modules
- 27
- Entity classes
- 80
- Migrations
- 34
- Lines of C#
- ~51k
What I built
- Multi-tenant design: one PostgreSQL schema and one file folder per school, created when a school is provisioned
- Super Admin tools to create schools, set branding and view platform statistics
- Admissions: students and guardians, online applications with approve or decline, and bulk CSV import with failed-row export
- Student and parent directories with search, filters, soft delete, export and login switches
- Employees, departments, salary templates, monthly payroll, advance salary, leave and awards
- Academic setup: classes, sections, subjects, class teachers, timetables and promotion
- Exams: terms, halls, mark distributions, schedules, mark entry, grade ranges and automatic ranks and GPA
- Attendance for students, staff and exams, with reports
- Fingerprint attendance from multi-device ZKTeco readers, routed to the right school by device serial number
- Library with books, issue and return, and fines; events with a public list
- Student accounting (fee types, invoices, offline payments, reminders) and office accounting with debit, credit and running balance
- Messaging with inbox, sent, important and trash; per-school email and SMS gateways with templates
- Roles and permissions matrix, sessions, scheduled jobs, database backup and login history
- Layered .NET solution, Swagger docs, Docker image and a CI pipeline that builds and publishes it
Architecture
System architecture
Multi-tenancy
Module map
Exam results
Biometric attendance
Engineering decisions
01A schema per school
Each school has its own PostgreSQL schema. Isolation is built into the database layout, so a mistake in a query cannot leak another school's rows.
02One shared storage bucket, one folder per school
Files live in a single object store with a folder for each school. Storage stays simple to run, and files are always found by the school name.
03Devices are identified by serial number
A fingerprint reader cannot send a login token or a school header. A registry maps each device serial to its school, so punches reach the right place without storing passwords on the device.
04Updates use PATCH only
Every update is a PATCH, never a PUT, so a client sends only what changed and cannot accidentally erase a field it did not know about.
05Bulk import with partial success
A CSV import saves the good rows and returns the failed rows as a file. A school can fix a few bad lines instead of re-uploading hundreds.
06Secrets are encrypted at rest
A school SMTP password is encrypted before it is stored and only decrypted when mail is sent. A database copy does not reveal mail credentials.
07Documentation that ships with the code
A module index and a system workflow document are updated as modules ship, so a frontend developer can build against the API without reading the source.
Need something similar?
Tell me what you are building and I will reply within one working day.



