Kartar Aarogyam Wellness Centre
Website and booking system for a wellness centre
A public website with Virtual OPD (online doctor consultation) booking, medicine inquiries and a secure admin dashboard. Forms are bot-protected and validated on both client and server; the admin panel manages bookings, content and a Mon–Sat schedule, with hourly reminder and cleanup jobs.
Not public yet.

- Built with
- Next.jsReactTypeScriptTailwind CSSshadcn/uiReact Hook Form + ZodCloudflare WorkersCloudflare D1Drizzle ORMWorkers KV / R2Cloudflare TurnstileVitestPlaywright + axe-core
- Type
- Client & collaborative work
- Source
- Private client repository
How it decides
Booking request → confirmed slot
- person
- guesses / untrusted
- decides
- process
- storage
- third party
- trust boundary
Boundary Where guessing stops and something deterministic decides.
- A partial unique index on appointments.slot_key makes double-booking impossible at the database level. repo docs
- Turnstile is verified on the server; without the secret, forms fail closed. repo docs
- Every admin handler calls requireAdminApi(); prescriptions are only served through an admin-only route from a private bucket. repo docs
The drawing, in words
Kartar Aarogyam Wellness Centre: Booking request → confirmed slot. Left of the redline is “Untrusted input”; right of it is “Enforced on the server”.
- Patient — books Virtual OPD (Untrusted input)
- Clinic staff — admin / super admin (Untrusted input)
- Booking form — RHF + Zod in browser (Untrusted input)
- Turnstile token — from the widget (Untrusted input)
- Route handler — Zod · origin · limits (Enforced on the server)
- Turnstile verify — fails closed (Enforced on the server)
- Admin API — requireAdminApi() (Enforced on the server)
- D1 appointments — unique slot_key (Enforced on the server)
- Hourly cron — reminders · cleanup (Enforced on the server)
- R2 prescriptions — private bucket (Enforced on the server)
Connections
- Patient → Booking form
- Booking form → Route handler (POST)
- Turnstile token → Turnstile verify
- Turnstile verify → Route handler
- Route handler → D1 appointments (insert)
- Hourly cron → D1 appointments
- Clinic staff → Admin API (session)
- Admin API → R2 prescriptions
More screens


The problem
- A clinic needs patients to book online consultations and send medicine inquiries without staff double-handling slots, and without exposing prescriptions or the admin side. repo docs
What was built
- Public pages, Virtual OPD booking, medicine inquiries with prescription upload, and an admin dashboard for bookings, content, doctors and the weekly schedule. repo docs
- Runs on Cloudflare Workers through OpenNext, with D1 + Drizzle for data, KV for page cache and a private R2 bucket for prescriptions. repo docs
- Unit tests in Vitest; end-to-end tests in Playwright check every public page for one H1, SEO tags, no console errors and WCAG 2.2 AA with axe. repo docs
My part
- Built the redesign of the public site (the “Parchi” visual system). commits
- Built the three-step appointment booking flow and the Razorpay checkout backend. commits
- Ran repeated critic-and-QA review rounds on the redesign — 56 items tracked in the first round alone. commits
Limits and open work
- Not launched yet.
- If R2 is not enabled, patients are asked to send prescriptions over WhatsApp instead of uploading them. repo docs
Decisions worth knowing
The database refuses double-booking
A partial unique index on appointments.slot_key covers every pending or confirmed booking, so a race between two patients ends in one insert failing — not in two confirmations.
repo docs
One schema, two places
The same Zod schemas validate in React Hook Form and again in the route handlers, so the server never trusts the browser's checks.
repo docs
Fail closed on bot protection
Every public form needs a Turnstile token. Without the server secret, forms refuse to send instead of silently skipping the check.
repo docs
A closer look
| Hour | Mon | Tue | Wed | Thu | Fri | Sat |
|---|---|---|---|---|---|---|
| 09 | ||||||
| 10 | ||||||
| 11 | held | |||||
| 12 | ||||||
| 13 | ||||||
| 14 | ||||||
| 15 | ||||||
| 16 | ||||||
| 17 | ||||||
| 18 |
- Request A · Tue 11:00
INSERT … slot_key✓ booked (pending) - Request B · Tue 11:00, same moment
INSERT … slot_key✗ unique index violation → slot taken