The challenge
Small Indian clinics run on paper, Excel and WhatsApp, while enterprise hospital systems are too heavy and too expensive for them to adopt.
The approach
Built a multi-tenant CRM as a pnpm monorepo: a NestJS + Prisma + PostgreSQL API, and one React/Vite + Tailwind + shadcn/ui frontend served both on the web and as a native desktop app through a Tauri v2 shell. Every clinic-scoped table carries a tenant key and every query is scoped from the authenticated session, never from client input.
The outcome
Patients, doctors, slot-based appointments with double-booking prevention, consultations, structured e-prescriptions and invoicing with split payments, with no server setup required from the clinic.
How MediConnect is put together
A pnpm monorepo with one rule: the clients hold no rules. A single NestJS API on Postgres serves the clinic web app, the same build wrapped as a Tauri desktop app, and a public Next.js website generated from each clinic’s CMS content. Tenant isolation, booking and billing are all enforced in the API.
- concurrent bookings, one winner (tested)
- 10 → 1
- NestJS domain modules
- 25
- Postgres models
- 49
- desktop installer (Tauri)
- ~3 MB
01 · Clients
client
Clinic staff
admin, doctor, reception
client
Patients
02 · Applications
service
Clinic web app
React · Vite · React Query
Vercel
service
Desktop app
Tauri v2
same web build inside
service
Clinic website
Next.js
per-clinic pages from the CMS
03 · API
service
Clinic API
NestJS · Prisma · Render
25 modules: auth, tenancy, booking, billing, audit
04 · Data
database
PostgreSQL
Supabase
all records, JSONB notes
object store
Object storage
Oracle OCI
documents and scans
9 connections
- Clinic staff→Clinic web appHTTPS
- Clinic staff→Desktop app
- Patients→Clinic websiteHTTPS
- Clinic web app→Clinic APIJSON · JWT
- Desktop app→Clinic APIJSON · JWT
- Clinic website→Clinic APIpublic overview
- Clinic API→PostgreSQLSQL
- Clinic API→Object storageupload · sign URL
- Clinic web app→Object storage5-min signed download · optional
Request flow · Booking an appointment under contention
- 1Reception picks patient, doctor and time; the app sends POST /api/v1/appointments with a 15-minute access token.
- 2The API validates the token and takes the clinic from the session, never from the request body.
- 3It checks the doctor’s schedule and inserts. A partial unique index decides any race: exactly one booking commits, the other receives 409 “slot just taken”.
- 4Audit and usage rows are written in the same request and the booking returns to the screen.
Design decisions
- The database settles double-booking
- Application checks can both pass under concurrency; a partial unique index cannot. Verified with ten simultaneous requests.
- Tenancy from the session only
- Every clinic table carries clinic_id, read from the signed-in session. A record from another clinic returns “not found”, so existence can’t be probed.
- Rotating single-use refresh tokens
- 15-minute access and 30-day refresh tokens; each refresh token works once, and replaying one is treated as theft and recorded.
- Shared contracts package
- Request and response types live in packages/types and compile into both API and clients, so a changed field breaks the build on both sides.
- Deliberately not used
- No MongoDB (Postgres JSONB covers flexible notes), no Redis (the index already prevents double-booking), no Docker (nothing to orchestrate yet).
Trade-offs & known limits
- The API runs as a single instance; scaling out needs the Redis slot-lock that is configured but not yet wired.
- WhatsApp, SMS and push dispatch are interfaces with logging stubs; no messages are delivered yet.
- The OTP-verified public booking API is built, but the patient-facing booking page that would call it is not.
Have something similar in mind?
I'm open for freelance and contract work — let's talk about what you're building.