DocSphere
Multi-tenant clinical practice platform for Indian doctors and clinics — one Expo codebase shipping iOS, Android, and web.
Client
DocSphere
Industry
Healthtech / clinical practice
Duration
Active development, Dec 2025 – present
DocSphere is a cross-platform clinical practice-management SaaS that is live on Google Play with paying subscribers in India and Italy. Solo doctors and multi-doctor clinics manage patients, digital prescriptions, appointments, and recurring follow-ups from one app that ships to iOS, Android, and web from a single Expo/React Native codebase. A shared Supabase backend handles auth, Postgres, storage, and row-level security across every platform, with a FIFO 5-device cap and trial-to-sales automation enforced in SQL rather than client code. The schema has survived 33 migrations — including a payment-provider swap and a single-tenant → multi-tenant refactor — without a rewrite. It is the one claim on this site anyone can verify in ten seconds:
Overview
The product
A multi-tenant clinical practice platform for Indian doctors and clinics. Built mobile-first with Expo, backed by Supabase, and shipped as one codebase across iOS, Android, and web.
A cross-platform practice management app that a solo doctor can sign up for in 30 seconds and a multi-doctor clinic can run their entire front desk on — one codebase, one Supabase backend, iOS, Android, and web.
The problem
Indian private practitioners live in WhatsApp and paper. The existing "clinic software" market is either expensive, built for hospital-scale workflows nobody wants, or abandoned. A friend running a small clinic described the job-to-be-done in one sentence: "I just want to write a prescription on my phone, hand the patient a copy, and know when to call them back."
That's where DocSphere started. It grew from there into a full platform once clinics — not just solo doctors — started asking for it.
What it does today
For the solo doctor
- Register patients with a full intake form (age, blood group, allergies, history).
- Write digital prescriptions with medicines, dosage, instructions, and file attachments.
- Schedule appointments and track status (scheduled / completed / cancelled).
- Set follow-ups with recurrence rules ("every 2 weeks for 3 months"), get local reminders at 9 AM on the due date, and fire off a WhatsApp reminder with one tap.
- Search the full patient history.
- 14-day free trial, then Stripe-backed subscription (₹1,500/month or ₹15,000/year), with promo codes and referral codes built in.
For the clinic owner
- Sign up once as a clinic, not a doctor.
- Add up to N doctor profiles under a single login.
- Tap a switcher to pick which doctor you're currently acting as — every patient and prescription you create gets scoped to that doctor.
- Filter the patient list and the search results by any doctor or "All doctors" — independent of who you're acting as for new entries.
- Team management screen: add, rename, remove doctors.
- 14-day trial → automated email to sales when it ends → custom plan activated in Supabase.
- FIFO 5-device cap at the account level so a clinic can't silently share credentials across 20 devices.
For the practice owner
The Practice tab turns the prescription history into a custom SVG bar chart of diagnosis frequency, a feed of overdue/today/this-week follow-ups, and simple patient-volume stats. No Chart.js — it's 120 lines of react-native-svg primitives because I wanted it to feel native and fast.
Tech stack
| Layer | Technology |
|---|---|
| Client | Expo SDK 54, React Native 0.81, Expo Router 6, React 19 |
| State & data | Supabase JS 2.58 (auth, Postgres, Storage, Edge Functions, Realtime) |
| Storage | AsyncStorage for session, device ID, language, notification IDs |
| UI | Lucide icons, custom theme tokens, Linear Gradient, Blur, Reanimated 4 |
| Charts | react-native-svg — hand-rolled bar charts |
| Notifications | Expo Notifications (push tokens + local reminders) |
| Payments | Stripe (checkout, webhooks, plan changes, promo codes) |
| Resend from a Supabase Edge Function | |
| Scheduling | pg_cron + pg_net running a daily trial-ended notifier |
| i18n | Custom Context-based i18n (English + Hindi) |
Scale of what's in the repo
27
Screens
~14k
LOC TypeScript/TSX
33
SQL migrations
11
Edge functions
20+
Tables with RLS
3
Platforms (iOS/Android/Web)
The engineering decisions I'm proud of
1. RLS policies that degrade gracefully from solo to clinic
The original schema assumed doctors.id = auth.uid(). When clinics landed, I needed a clinic owner to see patients under any of their clinic's doctors — without breaking the existing solo-doctor flow.
Solution: a single is_clinic_owned_doctor(uuid) STABLE SECURITY INVOKER helper, and every policy on patient-owned tables reads:
USING (doctor_id = auth.uid() OR is_clinic_owned_doctor(doctor_id))Individual accounts are untouched. Clinic owners get read/write reach across their staff. All in one file, re-runnable, no code duplication.
2. FIFO 5-device cap in SQL, not in a client-side timer
Clients can't be trusted to evict themselves — and distributed-locking "who's the 6th device" in application code gets gnarly fast. I moved the whole thing into a SECURITY DEFINER function:
-- Upsert the current device's row, then delete any rows beyond
-- the cap ordered by oldest last_seen_at. Both happen in the
-- same transaction.The client hook's only job is "fire this RPC on mount and on app foreground." Eviction is atomic.
3. Trial-ended notifier is an idempotent pg_cron job
Automating "email sales when a clinic's trial is up" using a pg_cron schedule + pg_net.http_post to a Supabase Edge Function. The function scans for subscription_status = 'trial' AND sales_notified_at IS NULL AND trial_start_date < now() - interval '14 days', emails sales via Resend, and stamps sales_notified_at so reruns are no-ops. Missed days, double fires, partial failures — all recoverable.
4. Follow-ups with client-side recurrence expansion
Instead of a second table for follow_up_occurrences plus a DB trigger to materialize them, recurrence is three columns (recurrence_frequency, recurrence_interval, recurrence_end_date) on the parent row, expanded client-side in a single useFollowUps hook. Trade-off: no server-side recurrence means a pathological user with a 1-day recurrence over 10 years would generate a lot of in-memory events — acceptable for this use case, and trivially upgradeable later.
5. One auth user → many synthetic doctor profiles
A clinic owner is a regular auth.users row. Each doctor they add is a doctors row with a synthetic UUID and a non-login email. The original doctors.id REFERENCES auth.users(id) FK was replaced with a BEFORE DELETE trigger on auth.users that cleans up the individual-account row — preserving cascade behaviour without blocking synthetic profiles.
6. Two independent doctor scopes — "acting" and "filtering"
The naive multi-tenant UX is one global "current doctor" pill that scopes everything — but that conflates two questions a clinic owner asks at different moments:
- "Whose chart am I writing on right now?" — answered by the acting doctor (controls what
doctor_idgets stamped on new patients/prescriptions/appointments). - "Whose patients do I want to look at right now?" — answered by the doctor filter on the Patients tab and Search screen (controls what gets queried).
Conflating them creates real friction: the owner wants to glance at Dr. Sharma's patient list while in the middle of writing a prescription as Dr. Mehta. Forcing them to flip the global pill loses their place and risks creating the next prescription under the wrong doctor.
So I split the axes. The acting-doctor pill stays on the home screen. A separate "All doctors / per-doctor" filter lives on the patient list and search — read-only, independent state, defaults to "All doctors" for clinic owners. Patient cards show "Dr. <name>" in brand colour whenever the result set spans multiple doctors.
What this project is not (and why that matters)
It's not a hospital information system. It's not an EMR with ICD-10 or HL7 compliance. It's not a telemedicine platform. Every one of those was a scope-creep request I said no to.
The product is opinionated: "a small practice can run on this, end to end, from trial to daily use, in under 5 minutes from signup." Every feature that didn't serve that got cut or deferred.
Result
DocSphere is live on Google Play with paying subscribers in India and Italy. The schema has survived 33 migrations including two major refactors (payment provider swap, then single-tenant → multi-tenant). The codebase is mine to iterate on — no framework lock-in, no managed-BaaS ceiling I'm going to hit at scale.
Built by Ojas Gangwal at Khaas. Want something similar?
Book a 30-min call →Need something like this?
Book a 30-min call. We'll scope your project together — no obligation.