Personal project (private repo)
QueueLK: Real-Time Queue Management for Clinics
A screen on the waiting-room wall and a page on the patient's phone, for Sri Lankan dispensaries, clinics and salons. Patients scan a QR code and see how many people are ahead of them, with nothing to install. The hard part was keeping token numbers gap-free and unique when everyone scans at once.
The problem
- In a Sri Lankan dispensary a patient takes a paper number and has no idea whether the queue is at 12 or at 40, so they either wait for hours or miss their turn.
- Thirty patients can scan the QR code in the same second while staff issue a walk-in at the counter, and every number must still be unique and in order.
- Two staff tablets pressing "call next" at the same moment must call exactly one patient.
Architecture and key decisions
- NestJS 10 API with Prisma 5 and PostgreSQL 16; a Next.js 14 web app with four surfaces (TV display, patient page, staff console, owner dashboard); and an Expo (React Native) patient app.
- Token numbers come from one INSERT … ON CONFLICT DO UPDATE … RETURNING on the per-day counter row, which takes the lock. A UNIQUE constraint on (queue, date, number) is the backstop, and the capacity check runs in the same transaction, so a rejected token never leaves a gap.
- "Call next" locks the queue row with FOR NO KEY UPDATE (plain FOR UPDATE blocked token inserts on their foreign-key check), then runs a conditional UPDATE … WHERE status = 'WAITING'. The idempotency key is written in the same transaction, so a double-tap replays the first response.
- Public screens listen over Server-Sent Events through Redis pub/sub. The staff console uses Socket.io with a Redis adapter, a JWT handshake and acknowledged commands.
- Web Push (VAPID) with a hand-written service worker, Expo push for the app, and an installable PWA with an offline fallback page for the token screen.
- Token events carry no personal data and go to Pub/Sub after commit. In Terraform they reach BigQuery through a BigQuery subscription; the emulator can't do that, so locally a small loader stands in. The GCP Terraform (Cloud Run, Cloud SQL on a private IP, Secret Manager, Workload Identity Federation) is plan-tested and has never been applied.
Results
- k6 morning rush on my laptop (300 patients in 60 s, 5 counters): no gaps, no duplicates and nobody called twice across about 5,400 tokens.
- Take-a-number p95 of 71–91 ms after moving issuance to the counter row only, against 91–132 ms before, in interleaved runs. The machine's load moved p95 more than the code did; p99 dropped in every paired round.
- The load test caught a regression I introduced. I stopped awaiting the display broadcast to save latency, which removed hidden back-pressure: the database pool ran dry, p95 went from 377 ms to 7.6 s and some requests failed. A per-queue coalescer fixed it, and I wrote a blameless postmortem.
- The GCP Terraform has 8 plan-time tests on mocked providers: no public IP on Cloud SQL, no owner or editor roles, and a budget.
- Integration tests run against real Postgres and Redis, including a real Socket.io server and a second API instance.
Tech stack
- TypeScript
- NestJS
- Next.js 14
- React Native (Expo)
- PostgreSQL
- Prisma
- Redis
- Server-Sent Events
- Socket.io
- Web Push
- PWA
- GCP (emulators)
- Pub/Sub
- BigQuery
- Terraform
- k6
- Jest
- Playwright
Links
- Architecture notes and code excerpts: github.com/jani358/queuelk-showcase. The main repo is private. Full source available on request.
- Case study by Janindu Gayanga, full-stack software engineer in Colombo, Sri Lanka
- Profile on GitHub and LinkedIn
Other projects
- AskLK: Bilingual AI Assistant with RAG, Agents and MCP: Multi-tenant RAG assistant that answers in Sinhala or English, with citations
- BookLK: Event-Driven Service Booking Marketplace: Multi-vendor booking with Kafka, a Go realtime service, payments and Kubernetes
- ApplyWise: CV Tailoring Where Every Line Cites a Fact: Each line of a tailored CV cites a fact from your own CV, and a verifier removes the rest
- iGaming Platform: Go Microservices, Payments and Compliance: Real-money gaming platform with 12,299 registered players, moving from a Node.js monolith to Go