Skip to content
← All projects

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

  1. 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.

  2. 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.

  3. 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.

  4. 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.

  5. 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.

  6. 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

The portal signs in with Firebase Authentication, and the API exchanges a fresh ID token for an HttpOnly session cookie. Every request then passes an origin check, a rate limit and a session check, resolves which workspaces the person belongs to, and goes through a role guard for the current centre. The route runs inside one transaction that switches to a narrow NOLOGIN role and sets the organisation with SET LOCAL, so row-level security on organization_id in PostgreSQL filters every row.

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

Next

Case studyImago →