Balaji Arumugam
Back to Projects
Full Stack

School Document Approval System

A full-stack, multi-tenant SaaS where every school is an isolated tenant with its own users, routing rules, and encrypted Microsoft 365 credentials. I designed and built the whole thing: a layered TypeScript/Prisma backend, a React 19 frontend, the Azure AD, SharePoint, and Teams integrations, and the real-time notification layer. It's deployed on my company's internal server ahead of go-live, and the entire codebase was built with Claude Code.

🏢 iKomet Technology Solutions2026
🔒 Source & live demo not available, built under NDA

About the Project

Schools run on curriculum paperwork: syllabi, curriculum plans, and the monthly lesson plans that teachers submit and department heads sign off on. IET had been managing all of it by hand, on top of a brittle Power Automate workflow. This platform replaces both with one system. A teacher uploads a document, a routing engine works out exactly who approves it (Teacher → HOD → VP → Principal), and that approver can act from the web app, an email action link, or straight inside Microsoft Teams. Admins own the users, permissions, master data, and the routing rules.

The biggest architectural decision was multi-tenancy. Every tenant-owned record carries a schoolId that scopes every read, and record creation won't compile without a tenant, so a query can't leak data across schools. A tenant-isolation test suite exists to catch it if that ever breaks. Each school resolves its own Azure AD, SharePoint, Teams, and email configuration from its tenant row. Those secrets are AES-256-GCM encrypted at rest and never sent back to the client. New schools are provisioned from a CLI or a developer console that seeds master data, permissions, and bootstrap users in one step.

The backend is a strictly layered TypeScript app: Routes, Controllers, Services, Repositories, then Prisma. Every endpoint is validated with Zod in strict mode, auth is a JWT in an httpOnly cookie, access runs through a dynamic per-role permission matrix, and there are four tiers of rate limiting. Uploaded files are checked by magic bytes rather than their extension, stored under UUID filenames outside the web root, and versioned on every re-upload. Each document moves through a PENDING → APPROVED / REJECTED / REVOKED state machine, and those transitions run inside database transactions that write the audit log in the same commit.

Most of the effort went into the Microsoft 365 side, so the portal sits inside the tooling schools already use. Login is Azure AD SSO through MSAL. Email goes out over Microsoft Graph. Approved documents sync to SharePoint automatically, and change-tracking webhooks (auto-renewed by a cron job) catch anything uploaded straight to SharePoint and pull it back in for review. Teams notifications carry the approve and reject buttons inline. There's also an HR insights module that polls a OneDrive/Forms export through Graph and syncs it into MySQL for a candidate-analytics dashboard.

I built the whole system with Claude Code. The architecture, the tenancy model, the security decisions, and the product calls were mine; Claude Code was the environment I implemented them in, down to the Vitest unit and integration tests. It's deployed and running on my company's internal server behind IIS and PM2, waiting on the school's go-live.

Core Concepts

🏫

Multi-tenant Isolation

Every tenant-owned model carries a schoolId that scopes each read, and record creation won't compile without a tenant, so cross-school access can't happen by accident. A default-school row handles the single-tenant case, and a tenant-isolation test suite re-checks the guarantee on every build.

🔐

Encrypted Per-School Integrations

Azure AD, SharePoint, Teams, and email config resolve per school from the tenant row, falling back to .env. The secrets are AES-256-GCM encrypted at rest and write-only: they go in, but the API never returns them, so a school's Microsoft credentials never leave the server.

🧭

Dynamic Approver Routing

A routing engine picks each document's approver at upload time. It checks three things in order: a per-uploader assignment, then attribute-based rules, then the school-wide default. The resolved approverId is frozen onto the document so the target can't drift if the config changes later. Admins configure and preview all of this without touching code.

🔗

Microsoft 365 Integration

Azure AD SSO through MSAL, transactional email over Microsoft Graph, and two-way SharePoint sync. Approved documents flow out to SharePoint, and files dropped straight into SharePoint get caught by auto-renewing webhooks and pulled back in as approval records. The portal also runs as an embedded Microsoft Teams tab app with Activity Feed notifications. Any of these can be left unconfigured for a given school, and that integration simply switches off.

🛡️

Security & Access Control

JWT in httpOnly Secure cookies, bcrypt at 12+ rounds, a dynamic per-role permission matrix (7 roles), Zod .strict() validation on every input, magic-byte file validation, Helmet CSP, four-tier rate limiting, and generic auth failures that prevent user enumeration.

Real-time Notifications

A JWT-authenticated Socket.io connection pushes approval and status events to a specific user's room rather than broadcasting, which drives the live unread-count bell. Each event also goes out over three independent channels: in-app, email, and the Teams Activity Feed. If one channel is down the others still deliver, and since every notification is saved to the database, none are lost.

Key Challenges & Solutions

Challenge: Guaranteeing Tenant Isolation by Construction

In a multi-school system, one missed schoolId filter on a single query silently leaks another school's documents, users, or audit trail. Trusting every developer to remember that filter on every query isn't a real safeguard.

Solution: I pushed schoolId into the data layer itself. Services read it from req.user and scope every query, and record creation won't type-check without a tenant, so nothing can be written unattached. Per-school integration secrets are AES-256-GCM encrypted and write-only. On top of that, a tenant-isolation integration suite asserts that cross-school reads come back empty, so if the guarantee ever regresses the build fails.

Challenge: Routing Every Document to the Right Approver

Different schools structure approvals differently, and within a school one teacher might need a specific reviewer while everyone else follows the org chart. Hardcoding Teacher → HOD → VP → Principal would break the first time someone needed an exception.

Solution: I built a routing engine that resolves the approver from a layered config: per-uploader assignments first, then attribute-based rules, then a school-wide default. The resolved approverId is frozen onto the document at upload, so editing the config later never re-targets a document that's already in flight. Admins get a preview tool to test routing before they save it, plus bulk import for setting it up at scale.

Challenge: Making Microsoft 365 Integrations Optional and Per-Tenant

Each school has its own Azure tenant, SharePoint site, and Teams channel, or none at all. The same codebase had to talk to a different Microsoft environment for every school, and keep working for the schools that hadn't set one up yet.

Solution: A school-integration service resolves config from the School row, falling back to .env for the default tenant. Each integration (SSO, Graph email, SharePoint sync, Teams) is feature-checked and switches off when it isn't configured. So a school with nothing set up runs on local auth and storage, a fully configured one gets the full Microsoft experience, and there isn't a single per-customer branch anywhere in the code.

Challenge: Building a Production System End-to-End with Claude Code

The goal was a real, deployment-ready multi-tenant product rather than a prototype, with the security, testing, and architectural discipline that implies, and I wanted to build all of it through Claude Code.

Solution: I kept ownership of the architecture and the product decisions, and used Claude Code to implement them: a strictly layered backend, Zod-validated endpoints, a Vitest unit and integration suite covering tenant isolation, and house rules like a 250-line file cap and no any types. It's deployed to the company's internal server behind IIS and PM2, ahead of go-live.

Tech Stack

Backend

Node.jsTypeScriptExpressPrismaMySQLZodSocket.ioWinstonnode-cron

Frontend

React 19TypeScriptViteChakra UI v3React Router v7ZustandTanStack QueryReact Hook Form

Integrations

Azure AD (MSAL)Microsoft GraphSharePointTeamsOneDrive

Security & Tooling

JWTAES-256-GCMbcryptHelmetVitestSwaggerClaude Code

Impact Metrics

Multi-tenant

Per-school Isolation

4

Microsoft 365 Integrations

7-role

Dynamic RBAC

Pre-launch

Deployed to Internal Server

My Role

Title

Software Engineer

Duration

2026 – Present

Key Responsibilities

  • Built the full stack with Claude Code: a layered TypeScript/Express/Prisma backend and a React 19 frontend
  • Designed the multi-tenant model: schoolId isolation on every model, per-school integration config, AES-256-GCM encrypted secrets, and CLI/console provisioning
  • Built the dynamic approver routing engine (identity assignments, attribute rules, school-wide default) with the approver frozen at upload
  • Integrated Microsoft 365: Azure AD SSO (MSAL), Graph email, two-way SharePoint sync, and an embedded Teams tab app with Activity Feed notifications
  • Implemented the security posture: httpOnly JWT cookies, dynamic permission matrix, Zod strict validation, magic-byte file validation, and rate limiting
  • Wrote the Vitest unit and integration test suite, including tenant-isolation guards, and deployed to the company's internal server (IIS + PM2)

Up Next

Editorial Workflow System

Visual workflow builder and RabbitMQ-driven review pipeline for editorial teams at iKomet.

Next Case Study