Case study

Kalinga

A multi-clinic web app that takes a veterinary clinic's appointment book off paper and remembers what every animal is due for next. Pet owners book from a phone without an account, staff run the day from one screen, and the clinic sees which vaccinations, dewormings and grooms are coming due. I built it alone in four days, and every clinic's data is kept apart by code that fails the build when a query forgets to.

Type
SaaS · Web App · PWA
Role
Solo Product Build
Built
September 2026
Client
Own product, for vet clinics in Northern Mindanao

Stack

Next.js 16 · React 19 · TypeScript · Drizzle · Neon Postgres · Better Auth · Resend · Tailwind CSS v4 · PWA

01

Why it exists

A small vet clinic in the Philippines runs on a paper logbook, a Facebook page for enquiries and a staff member's memory for who is due a vaccination. Vaccinations come round every year, deworming every quarter, grooming every four to six weeks. That is money the clinic has already earned the right to and routinely forgets to collect, because nobody looks back through the logbook.

I picked veterinary over dental after scanning qualifying businesses across Northern Mindanao. There were more vet clinics than dental clinics, the typical vet was busier, and a pet has several recall cycles running at once where a dental patient has one.

Kalinga is my own product. The brief says what it is not as clearly as what it is. No payments, because clinics take cash and GCash and v1 only records the reference. No clinical records beyond weight, vaccinations and visit notes. No SMS, because every Philippine gateway charges per message and that cost waits for a paying clinic.

The Kalinga landing page, with the clinic's day view beside a photograph of a dog
02

Booking without an account

Each clinic gets a booking page it can share on Messenger. A pet owner picks a service, a vet and a time on their phone and is done, with no account to create. The confirmation email goes out after the booking is saved, and if the mail provider fails the booking still stands. Someone standing in a queue on a phone should never see an error because an email didn't send.

Free slots come from an availability engine that was written and tested before any screen existed. It stores working hours as weekly rules plus exceptions and works out the slots when someone asks, in the clinic's own time zone. Storing a table of slots would mean keeping it in step with every booking, closure and change of hours, and that is where scheduling bugs live.

The engine is not the last line of defence. Two people confirming the same slot in the same second would both pass it, so Postgres has a constraint that refuses overlapping appointments for one vet. The second person sees that the slot was just taken.

03

Running the day

Staff work from a day view built so a receptionist can run a morning without scrolling past it. Check a pet in, see who has arrived, open the record, write what happened. The vet adds weight, vaccination dates and visit notes, and those dates are what drive recall.

Recall is the part a clinic actually wants. One screen lists every animal coming due, and the clinic sets how long each kind of visit lasts before the next one is due. Reminder messages are written and logged in the app, and staff send them through whatever channel they already use.

04

Many clinics, kept apart

Each clinic is its own organisation, and every table that holds a clinic's data carries that clinic's id. The only way to read or write those tables is through one class that takes the clinic's id when it's created and adds it to every query. A test reads the source tree and fails the build if any other file reaches the database directly. A missing filter is the one bug that would end the project, so it's caught before a deploy rather than in a clinic's data.

There are four roles, owner, vet, front desk and pet owner, and each is enforced on the server. Every staff page checks the permission it needs and returns a 403 when the role lacks it, and a test fails if a new page is added without that check. Data a role can't see is left out of what the server sends, so hiding a button is never the only thing standing in the way.

05

Key decisions

Four choices did most of the work, and each one is in the public decision log with its reason.

No separate API

My client work runs a NestJS API because three portals share it. Here a second service would double what has to be deployed and what can fail, so Next.js server actions and route handlers do the job.

Time zones before code

Every timestamp is stored in UTC, each clinic has a time zone that defaults to Manila, and the screens show clinic time with the zone labelled. Someone booking from abroad still has to arrive at nine in the morning local time.

An audit trail that respects erasure

Deleting a client rewrites the audit entries that pointed at them rather than removing them. The record of what happened stays, the person it happened to does not, and the deletion itself is logged with a reason.

Accessibility in the build

The test suite renders the app's screens with realistic data and runs axe against WCAG A and AA on each one. It runs inside the production build, so a missing label stops a deploy.

06

Where it stands

Kalinga is live and every page in the plan runs against a real database. No clinic is using it yet. The plan was always to build to the point where it can be shown, then take it to one clinic from the regional scan before building anything else, and that conversation hasn't happened. Until it does, the domain stays a subdomain.

The source is public, so the claims here can be checked. There's no demo clinic yet, so seeing the staff side means creating a clinic of your own on the live site.

Up Next

Fiscplus

A compliance-grade SaaS portal for Belgian accounting firms.

SaaS · Web App