A friend of mine runs a dental practice. It's a real, busy clinic — not a side hustle — and it works fine day to day. But she had one nagging problem: keeping track of each patient's teeth over time.
Think about it. A patient comes in, gets a filling on one tooth, comes back six months later with something on another. Which teeth were treated? What was the condition last time? On paper, that history lives across a bunch of folders and a phone full of intraoral photos with no real order. When you've got hundreds of patients, "what's the status of this person's teeth?" becomes a genuinely annoying question to answer.
So I offered to build her something. Here's how it went.
What actually needed solving
The temptation was to build "a clinic app" and end up with a generic dashboard nobody wants to use. Instead I focused on the actual pain: seeing a patient's dental status at a glance, and never losing the history.
Turns out a dental record is pretty specific. Each patient needs:
- basic info + anamnesis (allergies, medical history)
- an odontogram — the tooth chart dentists use to mark the condition of every tooth
- a history of visits with notes
- photos — intraoral, extraoral, radiographs — tied to the visit they came from
The visit notes follow a standard dentists already use called SOAP. Getting these details right early is what kept it from feeling like a spreadsheet with extra clicks.
The stack
Next.js and TypeScript for the app, Supabase for the backend (database, login, file storage, and access rules all in one), Tailwind + shadcn/ui for the interface, Vercel for hosting.
Supabase mattered here because this is real patient data, not a to-do list. It lets me lock down "who can see what" at the database level instead of just trusting the frontend to behave. Photos live in a private bucket and are only handed out through temporary links. There are three staff roles — dentist, receptionist, nurse — and only clinical staff can touch diagnoses and the tooth chart. That felt like the right floor for medical info.
The fun part: the tooth chart
I figured I'd just grab a library for the odontogram. Nope — there's no good, maintained one for React. So I built it from scratch in SVG.
My first version was too basic: click a tooth, color it in. Not good enough. A real odontogram marks conditions per surface — every tooth has five (mesial, distal, occlusal, buccal, lingual), and a dentist records a cavity or filling on a specific surface, not the whole tooth. There's also a separate set of whole-tooth states: missing, crown, implant, root remnant, and so on.
There's even a sneaky detail: "mesial" is the surface facing the middle of the mouth, so which side that is flips depending on which quarter of the jaw you're in. Once I handled that, the chart started looking like something a dentist recognizes instantly instead of a toy. That's the part I'm most proud of — and it's exactly what fixes her original problem. Now a patient's whole dental status is one screen.
A face for the clinic too
While I was at it, I added a public homepage — services, the dentists, hours, location — and an online booking form where someone can request an appointment (pick a date and morning/afternoon), no account needed.
That created a neat little puzzle: the booking form is public, but everything behind it is private patient data. So the public can create a booking request but can never read anyone's. Staff get a scheduling board with a calendar (dates with bookings show a dot), search, one-tap WhatsApp to confirm, rescheduling, and a way to link a request to an existing patient.
Making it not look like an admin panel
I wanted it to feel like an actual product. Calm clinical vibe — teal on soft off-white, dark sidebar — with Bricolage Grotesque for headings and IBM Plex for everything else. My favorite touch: record numbers, tooth numbers, and stats are all in a monospace font, so the whole thing reads a bit like a real dental chart. Tiny decision, big difference.
What I took from it
- The best hour I spent wasn't coding — it was learning how a dental record actually works. Everything after that got easier.
- With real patient data, "who can see what" has to be baked in from the start, not bolted on later.
- Sometimes there's no library and you just build the thing yourself. The tooth chart is proof it's worth it.
It's still a work in progress — reminders and PDF exports are on the list — but it already does the job it was meant to: her team can pull up any patient and see exactly what's going on with their teeth. Building something a friend uses every day beats any tutorial project.