Designing APIs for a City-Wide Doctor Booking Platform
Specialty filters, city-wide search, and appointment state management - the API decisions that mattered most.

Building the API layer for a doctor appointment platform looks simple on paper: list doctors, book an appointment, done. The complexity shows up in the edges - what happens when two patients try to book the same slot, how search should behave when a city has twenty cardiologists and two dermatologists, and how much the frontend should know about appointment state versus how much the API should enforce.
Search Had to Do More Than Filter
City-wise search with specialty filters sounds like a simple query, but doctor availability changes constantly, and patients care about more than a name match - they care about whether a doctor is actually bookable soon. I ended up treating "search" and "availability" as related but separate concerns: the search endpoint returns doctors matching location and specialty, while a lighter availability check happens closer to the booking step, where the data needs to be fresher.
Appointment State Belongs on the Server
Early on, I let the frontend track whether a slot was "pending" versus "confirmed." That was a mistake. Two patients could both see a slot as available, both submit a booking, and the frontend had no way to know it had lost a race it didn't know it was in.
The fix was moving appointment state entirely server-side, with the booking endpoint treating slot assignment as the single authoritative action - the first valid request wins, and every other client gets a clear, honest "no longer available" response instead of a UI that silently let two people believe they'd booked the same hour with the same doctor.
Feedback as Its Own Resource, Not an Appointment Field
I initially modeled patient feedback as a field on the appointment record. It worked until the product wanted to show a doctor's feedback history independent of any single booking. Splitting feedback into its own resource, linked to but not nested inside appointments, made that query straightforward instead of requiring a join across every appointment a doctor had ever had.
The broader pattern: model the thing that will be queried on its own later as its own resource now, even if it feels like premature structure. The alternative is a migration once someone asks for a view you didn't design for.
