Build Guide / 03
System architecture
Two runtimes, one repository. A client SPA talks to Firestore directly for almost everything, and drops down to a thin Express layer only for the handful of things a browser shouldn't be trusted to do alone.
Request flow
Browser (React SPA)
│
├─ Firestore SDK ──────────────► Cloud Firestore
│ (reads/writes guarded by (patients, billing, inventory,
│ firestore.rules per role) attendance, settings...)
│
├─ Firebase Auth ──────────────► Google / email+password sign-in
│
└─ fetch('/api/...') ──────────► Express app (server-app.ts)
│
├─ firebase-admin (privileged writes,
│ bypasses client security rules)
├─ @google/genai (AI assistant)
├─ node-cron (scheduled bed charges)
└─ resend (email)
Why a server exists at all in a Firestore-first app
Most CRUD in EBsoft HMS goes straight from the React client to Firestore,
governed by firestore.rules. The Express layer exists for the
narrow set of things that genuinely need a trusted server:
- Scheduled work — a cron job checks every minute
whether it's time to post that day's IPD bed charges, using
getAdminFirestore()to write with elevated privileges. - AI assistant — the Gemini API key never reaches the browser; requests are proxied through the server.
- Cross-cutting operations that would be unsafe or unenforceable purely through security rules.
Frontend composition
Inside src/, cross-cutting concerns are lifted into React
Context so any of the ~90 page modules can read them without prop drilling:
| Context | Responsibility |
|---|---|
| AuthContext | Current user, role, sign-in/out |
| SettingsContext | Hospital-wide configuration (billing rules, timezone, branding) |
| NotificationContext | Staff-facing alerts and toasts |
| PatientPortalContext / PatientNotificationContext | Session and alerts scoped to the public patient portal |
Domain logic that would otherwise be duplicated across pages is pulled into
custom hooks — useIPDBilling, usePayroll,
useAttendance, useLeave, useRoles,
useDepartments, useEmployees — each wrapping
the relevant Firestore collection with the reads, writes and derived state a
given module needs.
Shared library layer
src/lib/ holds the utilities every module leans on rather than
reimplementing: firebase.ts (SDK init + auth helpers),
autoNumbering.ts (sequential invoice/admission IDs),
auditLogger.ts (who-did-what trail), dateUtils.ts,
referralCommission.ts (referral-doctor payout math),
sanitizeHtml.ts, imageCompressor.ts, and
bangladeshData.ts (districts/upazilas for address fields).
Role-based access
Access control is enforced twice, deliberately: firestore.rules
is the source of truth (a client can't bypass it no matter what the UI
does), and the React app additionally hides navigation and actions a role
shouldn't see, via UserPermissionsWidget and role checks in
AuthContext. See Database &
Security for how roles are modeled.