Case study · In progress
nfascent
A multi-tenant SaaS for tutors and small learning centres, built on my own. Each centre is a tenant, isolated by row-level security in PostgreSQL rather than by filters in application code.
- Role
- Personal
- Status
- In progress
Problem
Tutors and small learning centres manage classes, rosters and tests across spreadsheets and chat apps, while school LMS products are built for large institutions. nfascent is one platform that many tutors and centres can use, each with their own data.
Approach
- 01
Standards first. Classes, enrolments and people follow 1EdTech OneRoster and Ed-Fi; tests and questions follow QTI, so data can move in and out of other school systems.
- 02
Tenant isolation in the database. Every tenant table has row-level security on organization_id. Each request passes an origin check, rate limit, session check and role guard, then runs in one transaction that switches to a narrow NOLOGIN role (app_api, app_grading, app_notify and others) and sets the organisation, account and person with SET LOCAL. A missing WHERE clause cannot leak another centre's data.
- 03
One identity, many workspaces. Sign-in belongs to the person and profiles belong to each organisation, so one teacher can work at several centres. Firebase Authentication handles email, password and Google; the server exchanges an ID token issued in the last five minutes for an HttpOnly session cookie, then resolves which workspaces the person can enter.
- 04
Parents without passwords. An admin sends a link to the phone number on file. The database keeps only a SHA-256 hash of the token and its expiry, allows three links per parent per day, and binds the link to the first device that opens it.
- 05
Schema as the source of truth. 27 migrations across platform, roster, content, delivery, grading and notify schemas. SECURITY DEFINER functions pin their search_path, and 623 test scenarios check the access rules.
- 06
Frontend. Student, staff and parent portals in React with TanStack Router and Query and HeroUI, sharing auth, API contract and utility packages in a monorepo. Caddy serves each centre on its own subdomain.
Request flow
Result
In development. Sign-in, workspaces, parent links and the roster and delivery APIs run against the real database; the content, grading and billing modules are next, and the portals use a mocked API for those parts until then.
- TypeScript
- React
- TanStack
- Fastify
- PostgreSQL
- Firebase Auth
- Caddy