Multi-tenant SaaS School Management System for private schools in India — CBSE, ICSE, state board, and unaffiliated.
The tenant is the Organization (a Trust or Society), not the school. A school is a
branch under an org. This is the single most important architectural fact in the
codebase — see docs/DECISIONS.md ADR-001.
Working on this repo, human or AI? Read AGENTS.md first, then docs/TASKS.md for what is done and what is next.
Turborepo + pnpm · Express + tRPC v11 · Next.js 16 · PostgreSQL + Drizzle · Zod 4 · better-auth · Tailwind v4 + shadcn
pnpm install
cp .env.example .env # then fill in DATABASE_URL and BETTER_AUTH_SECRET
pnpm db:migrate
pnpm db:seed # demo org, two schools, two logins
pnpm dev # web :3000, api :4000Requires PostgreSQL 15+, Redis, and Node 20+. Redis backs the authz cache, so the API will not serve an authorized request without it.
The seed is idempotent and creates one org (demo-trust) with two schools and two
logins — [email protected] (org_admin, sees both schools) and
[email protected] (scoped to one) — password Password123! for both.
With pnpm dev running, pnpm smoke:authz proves the authorization spine end to end:
who may see which school, and who gets a 403.
pnpm dev # all apps
pnpm build
pnpm check-types # tsc --noEmit everywhere — the gate; must be green
pnpm lint
pnpm test # vitest — @repo/authz scope maths
pnpm db:generate # drizzle-kit generate
pnpm db:migrate
pnpm db:studio
pnpm db:check # connectivity, both Neon endpoints
pnpm db:verify # asserts the ADR-019 CHECK constraints still bite
pnpm db:seed # apps/api/scripts/seed.ts (ADR-020)
pnpm smoke:authz # end-to-end authz check; needs `pnpm dev` runningScripts are wrapped in dotenv -e .env, so one root .env feeds the whole monorepo.
apps/api Express host — better-auth, tRPC at /trpc, REST at /api, docs at /docs
apps/web Next.js App Router
packages/db Drizzle schema + client
packages/contracts Zod schemas derived from the schema
packages/services Business logic
packages/trpc Routers
packages/auth better-auth instance
packages/env createEnv() helper
Types flow one direction — db → contracts → services → trpc → web — so a column change
surfaces as a compile error in the client. apps/web imports only type AppRouter.
| File | Contents |
|---|---|
| AGENTS.md | Entry point: stack, commands, the 12 hard rules |
| docs/PRD.md | Scope, personas, deliberate non-goals |
| docs/ARCHITECTURE.md | Boundaries, request lifecycle, authz |
| docs/DOMAIN.md | The 63 tables and the critical flows |
| docs/CONVENTIONS.md | Naming, Drizzle patterns, anti-patterns |
| docs/DECISIONS.md | ADR log — read before changing architecture |
| docs/TASKS.md | Phased backlog and where to resume |
Phase 1 — foundation tables and the authorization spine, live on Neon and proven
end to end by pnpm smoke:authz. Phase 2 (academic structure) is next; see
docs/TASKS.md.