The Technology Behind MessMaster: A Full-Stack Architecture
MessMaster isn't a weekend project. It's a production-grade system built on a modern, battle-tested technology stack designed for speed, security, and scale.
Contents9 sections
- Member: uses the app without thinking about the stack — it just works.
- Meal manager: benefits from realtime updates, automatic calculations, and zero server maintenance.
Your available controls and calculations depend on your role, mess status, deployment, and current server data. This guide uses fictional examples and does not replace the values shown in the app.
Guide status: Labels, permissions, calculation rules, rates, costs, exports, and available controls can vary by deployment and mess configuration. Use the current screen as the source to check and verify saved records after important changes.
The architecture overview
MessMaster is split into two deployables from one repository: a Vite SPA (Vercel) and an Express API (Render). In production, Vercel rewrites /api/* and /socket.io/* to the Render backend and everything else to /index.html.
The five architecture layers are:
- React 19 UI — single-page application with React Router 7 and Zustand state management.
- Express 5 API — REST endpoints plus Socket.IO for realtime events.
- Prisma 7 ORM — type-safe database access over a
pg.Pooldriver adapter. - Supabase Auth — JWT-based identity shared across HTTP and Socket.IO.
- Cloudinary — image and receipt storage with automatic cleanup.
Each layer has a clear boundary. The frontend never calls the database directly. The backend never serves the SPA in production. Identity flows through JWTs, not session cookies.
Frontend: React 19 and friends
The frontend is a modern React stack:
- React 19 with concurrent rendering and Suspense support
- React Router 7 for route definitions (though the actual routing is a state machine in
useAppCore) - Zustand for lightweight state management with slices for auth, mess, payments, fines, and UI
- Tailwind 4 for styling with a custom design system
- Socket.IO client for realtime updates
- Framer Motion for animations
All API calls go through a configured axios instance in services/api/config.ts. No component calls fetch directly.
State management
Zustand stores are split into slices: authStore, messStore, paymentStore, fineStore, monthTransitionStore, and uiStore. Each slice owns a specific domain — no god store.
Routing as a state machine
Real routing is currentView state in the app core (useAppCore.tsx), bidirectionally synced to the URL by useRouteSync. The router.tsx file is only a canonical route constant table — it defines no routes. This design allows fine-grained control over navigation transitions and guards.
Backend: Express 5 and Prisma 7
The backend is an Express 5 application with:
- Prisma 7 with the
@prisma/adapter-pgdriver adapter over apg.Pool - Zod for request validation (422 responses with field-level error details)
- Joi as a legacy validator in a few remaining call sites
- Socket.IO sharing the same JWT secret as HTTP for realtime auth
- JobLock for safe cron-like background jobs
Response envelope
All responses use a standard envelope: { success, data?, error?, message?, code?, field? }. The global error handler sanitizes 5xx messages so stack traces never reach clients.
Validation layers
Two validators coexist by layer — don't mix them:
- Zod via
validate(schema)middleware validatesreq.bodyand returns 422 with field-level details - Joi remains for a few legacy call sites
Database: Postgres with Supabase Auth
Supabase Auth and Postgres/Prisma are parallel systems linked by UID, not by foreign keys across systems.
- Supabase Auth owns credentials. The JWT
subclaim is the identity used everywhere. - Prisma owns all application data.
Owner.id,PortalMember.id, andMessMember.userUidare Supabase UIDs. mess_code + membership_idis the app's composite identity, distinct from auth identity.
Schema management
prisma/schema.prisma is the source of truth. prisma/migrations/ is authoritative. supabase/migrations/ holds RLS policies and security hardening SQL — they are not mirrors.
Soft deletes
Messes use softDeletedAt and permanentDeleteAt for soft deletion. A cleanup service permanently purges deleted messes and drains a Cloudinary deletion queue with retry.
Realtime: Socket.IO event flow
Realtime is powered by Socket.IO, sharing the same JWT secret as HTTP. This means a single token authenticates both REST and WebSocket connections.
The data flow is:
- A user action triggers a Socket.IO emit.
- The Express handler validates and persists the change via Prisma.
- The server broadcasts the update to all connected clients in the same mess.
- The Zustand store updates and React re-renders.
Event types
- Meal updates — tracker changes sync across all members
- Payment status — approve/reject notifications
- Member join — role assignment notifications
- Month transition — balance carry updates
Identity and security model
Authentication is stateless JWT verification — requireAuth verifies the JWT locally with jwt.verify(token, SUPABASE_JWT_SECRET). It does not call Supabase.
Token invalidation is layered:
- Logout → exact-token blacklist
- Session revoke →
revoked-before:<uid>:<ts>marker compared to tokeniat - Owner sessions →
OwnerSessionrows add a second expiry check
Role guards
requireMessManager— mess admin accessrequireMessMember— member accessrequireOwnerRole— platform owner accessrequireOwnerOrManagerRole— shared adminmessStatusGuard— only admitsstatus === "active"
Rate limiting
Per-purpose rate limiters: loginLimiter, registerLimiter, passwordResetLimiter, apiLimiter, emailTriggerLimiter. trust proxy is set to 1 (not true) in production so IP-keyed limiters see the real client behind Vercel/Render.
File storage and email
Cloudinary
Image and receipt uploads go to Cloudinary. A cleanup service drains a MessCleanupQueue for asset deletion with retry logic.
Email sends go through a failover chain: brevo → mailersend → resend → smtp. The system falls through on provider failure or daily-cap exhaustion. Email volume is capped per provider per day.
Background jobs and scheduling
Background jobs run inside the API process using cron-like scheduling with JobLock for safety:
- Low-balance notifications (6h)
- Monthly statements
- Bazar reminders (8PM Asia/Dhaka)
- Cloudinary deletion retry (30min)
- Tracker-stats refresh queue (5min)
- Home/tracker stats reconciliation (6h)
- Self-ping keep-alive (10min)
If multiple instances run, JobLock prevents duplicate execution.
Deployment: Vercel and Render
- Frontend (Vite SPA) deploys to Vercel.
vercel.jsonrewrites/api/*and/socket.io/*to Render. - Backend (Express API) deploys to Render.
- In production,
server.tsdoes not serve the SPA — Vercel handles static files. - Locally, Vite's proxy simulates this architecture.
The build pipeline runs npm run typecheck and npm test -- --run in CI. Both must pass before merge.
Important: This guide describes the architecture as implemented. Specific configurations, rate limits, email providers, and job schedules can vary by deployment. Always verify the live configuration before making changes.


