We built ClinicPro as a single Next.js 15 application on MongoDB where one installation serves many clinics, and designed the safety properties into the data layer rather than leaving them to code review.
Key design decisions
- Shared database, enforced tenancy. 24 of the 39 Mongoose models are tenant-scoped. A schema plugin throws on any find, update, delete, count or aggregate that lacks a
clinicId filter; cross-tenant access needs an explicit option used only by the Super Admin console.
- One mutation pipeline. Every server action is built from guarded wrappers (read-only check → session and tenant → permission → zod validation → handler → audit → revalidate). A repo script fails the build if an action skips them.
- Scoped RBAC. 9 roles and 20 permission keys, each granted at an
all, read or self scope, shared by the server guards and the UI.
- Money done in integer cents. USD storage, a per-clinic display currency, an exchange-rate snapshot on every invoice and largest-remainder allocation so ledger rows always sum exactly.
- Integrity in the database. A unique partial index prevents double-booking; pharmacy dispensing picks batches first-expiry-first-out with optimistic concurrency.
- Health-data protection. AES-256-GCM field encryption for identifiers and clinical notes, rate-limited sign-in, session revocation and an audit trail.
- AI as an assistant only. ICD suggestions and prescription safety checks are schema-validated, labelled as suggestions and never applied automatically.
Architecture
React Server Components render the staff app, patient portal, kiosk and waiting-room display. Server Actions and route handlers (/api/v1 for staff, /api/admin, PDF streams, the portal, a public queue endpoint, a daily cron and the Stripe webhook) call domain services in lib/services, which use Mongoose on MongoDB. Integrations sit beside the services: Stripe Checkout, Cloudinary, SMTP and Google Gemini. A Super Admin console provisions clinics, gates modules by plan and edits the CMS-driven public website.