- Client
- A multi-vertical healthcare marketplace, India
- Status
- Beta: web live, Android in internal testing
- Built
- May to September 2026
- Platforms
- Web and Android
- Stack
- Next.js, NestJS, PostgreSQL, Flutter
Summary
The platform in brief.
The platform is a multi-tenant healthcare marketplace that lets hospitals in India onboard their doctors and lets patients find a doctor by specialty, book a slot, pay with Razorpay and join a video consultation. It is built to hold six kinds of tenant: hospitals, psychology clinics, IVF clinics, law firms, detective agencies and e-commerce vendors. Hospitals come first, and the other five are modelled for a later phase.
Every tenant's data sits in one PostgreSQL database, and row-level security keeps each tenant's rows out of every other tenant's queries. Patients prove their identity through DigiLocker, and their Aadhaar number never reaches the API. Doctors are checked against the national medical registry. Metageeks built the Next.js web app, the NestJS API and a Flutter Android app between May and September 2026. The web app is live in beta, and the Android app is in internal testing.
The brief
One platform, many tenants, none of them able to see each other.
The client set out to run a healthcare and lifestyle marketplace for India, hosted in the AWS Mumbai region. Hospitals join as tenants and bring their own doctors. Patients find a doctor by specialty, book a slot and pay online.
Four kinds of user share the platform: patients, doctors, the admins and staff of each tenant organisation, and a platform super admin. The data model also defines five tenant types beyond hospitals: psychology clinics, IVF clinics, law firms, detective agencies and e-commerce vendors. Those verticals share the same core, so the boundary between tenants had to hold for all of them.
The challenge
Identity, payments and video, all regulated.
Every party on the platform has to be verified. Patients prove who they are with Aadhaar through DigiLocker. Hospitals are checked as companies against their CIN, GSTIN or MSME registration. Doctors are verified against the national medical registry before they can accept patients.
Payments run through Razorpay and consultations happen over live video on LiveKit. Every booking, payment and consultation belongs to one hospital and has to stay invisible to the others.
The launch also had to stay lean, with one API, one web app and one database to deploy and run.
The approach
A modular monolith with the tenant boundary in the database.
Metageeks built one NestJS 11 application that serves the API and runs the background workers, split into modules whose boundaries CI checks with dependency-cruiser. A single Next.js 16 app serves the web. Prisma talks to PostgreSQL 16, Better Auth handles sign-in and CASL decides what each role may do.
Every domain table carries a tenantId column, and PostgreSQL row-level security (RLS) returns only the rows for the tenant set on the current transaction. Slow work, such as checking a doctor against the medical registry, runs as BullMQ jobs on Redis. The Android app is written in Flutter and calls the same API with bearer tokens.
For patients
From sign-up to a video consultation.
A patient signs up with a password, a magic link, a phone OTP or a Google account, then completes an identity check. They find a doctor by specialty, see the doctor's fee and next available slot, and book. Booking stays locked until the identity check is done.
Payment goes through Razorpay. At the booked time, patient and doctor join a LiveKit video room. The first working version of this flow, from sign-up through identity check, doctor search and booking to the video call, ran eight days after the first commit.
For hospitals and doctors
Onboarding a hospital, verifying a doctor.
A hospital joins through an email invite. Its company details are checked against CIN, GSTIN or MSME records, and its admin signs the platform agreement electronically. The admin can invite staff and doctors before the company details are complete, and each invite link works once.
A doctor who joins enters their medical registration number. The platform checks it against the national medical registry as a background job, and the doctor can accept patients once it passes.
Patient identity runs through DigiLocker, so the raw Aadhaar number never reaches the API. The server ignores the result the app reports and fetches the record from the KYC provider itself. A webhook, a finalize call and polling can each settle the check, and whichever arrives first wins. The stored record is encrypted with AES-256-GCM under rotatable keys and purged after a retention period.
The key decision
Isolation enforced where it cannot be forgotten.
The most important decision was where the tenant boundary lives. If application code filters by tenant, every query has to remember its WHERE clause, and one forgotten filter shows a hospital another hospital's records. Metageeks put the boundary in PostgreSQL: row-level security policies on each tenant table return only the rows for the tenant set on the current transaction.
Before launch, Metageeks found and fixed a gap in that design. On a pooled connection that had been used before, the tenant setting fell back to an empty string, and the policies read an empty string as the super admin bypass. The fix made the bypass an explicit sentinel value, extended RLS to 8 more tables and wrapped the 12 queries that depended on the old behaviour.
A review of the access-control path found 13 defects, 6 of them high severity. An invite with no role granted owner, some controllers had no guard, and the request body could override the session's tenant. The fix introduced a multi-role model, a partial unique index that lets a user be admin in only one organisation, and roles scoped to the transport, so a mobile session drops roles meant for the browser.
Access
Four kinds of user, each with its own limits.
CASL rules decide what each role can do, and the API checks them on requests. Row-level security sits underneath, so a query inside one tenant's transaction cannot return another tenant's rows.
Patient
Signs up, completes the identity check, books and pays for consultations and joins the video call.
Doctor
Joins through a hospital invite, verifies their medical registration and runs video consultations.
Hospital admin
Completes the company checks and the e-signed agreement, then invites staff and doctors.
Platform admin
Approves tenants and reviews KYC. Signs in with TOTP, and support access into a tenant is time-limited and audited.
Technical detail
Under the hood.
The technical choices behind the platform, and what each one does.
- PostgreSQL row-level security
- Every domain table carries tenantId. Policies return only the current tenant's rows, and the super admin bypass needs an explicit sentinel value.
- CASL authorization
- Role rules checked by the API, with roles scoped to the transport so a mobile session carries only mobile roles.
- Better Auth
- Password, magic link, phone OTP and Google sign-in, two-factor authentication, and bearer tokens for the Android app.
- AES-256-GCM payload encryption
- KYC payloads are sealed with a key identifier, so keys rotate without re-encrypting old records, and are purged after a retention period.
- BullMQ workers
- Registry checks and other slow jobs run as queued jobs on Redis, in the same NestJS codebase as the API.
- CI against real PostgreSQL
- Pushes to the main branches build, lint and run the tests against PostgreSQL with RLS policies applied, then check module boundaries with dependency-cruiser.
- Flutter mobile app
- An Android-first app for patients and doctors, on the same API as the web app.
- AWS hosting
- The web app runs on AWS Amplify, and the API ships as a Docker image to EC2 in the Mumbai region.
Outcome
Where it stands.
The web app is live as a beta. Payments and KYC still run on test and sandbox keys, and the Android app is in internal testing.
Hospitals are the first tenant type. Psychology clinics, IVF clinics, law firms, detective agencies and e-commerce vendors are modelled on the same core for a later phase. An AI assistant is planned and has not been built.
Good fit
For teams building something regulated.
The same approach suits platforms where several organisations share one product and their data must never mix.
- Several organisations share one product, and each must see only its own data
- Users prove who they are with Aadhaar, company or professional registrations
- Payments and live video sit inside the core flow
- The product launches with one vertical and has more planned
Real questions
Common questions
How do you keep one tenant's data away from another's?+
Every tenant table carries a tenant ID, and PostgreSQL row-level security returns only the rows for the tenant set on the current transaction. The tenant comes from the signed-in session, never from the request body. On this platform the super admin bypass also needs an explicit sentinel value, so an empty setting cannot open every tenant's rows.
How does Aadhaar-based KYC work without storing Aadhaar numbers?+
The patient completes the check inside DigiLocker, so the raw Aadhaar number never reaches the platform's API. The server then fetches the verified record from the KYC provider itself instead of trusting the app, stores it encrypted with AES-256-GCM and purges the stored payload after a retention period.
Can one codebase serve hospitals and other verticals?+
Yes. This platform's data model defines six tenant types: hospitals, psychology clinics, IVF clinics, law firms, detective agencies and e-commerce vendors. Hospitals are onboarded first, and the other five are planned for a later phase on the same API, database and tenant isolation.
Do clients own the code Metageeks writes?+
Yes. Our engagements end with the source code handed over, so you can run it, change it or bring in another team without asking us.
Your platform
Tell us who needs to use it.
Describe the organisations, users and checks your product has to handle, and we will tell you how we would build it and what it would take.


