Designing a Complete Delivery-Management System as a Solo Developer
TIL that a system spanning a browser extension, a React Native app, an API, and a database only stayed maintainable solo because I treated the API as the one real product and everything else as a thin client of it.
The Problem #
The full system — a Chrome extension pulling orders from the POS, a Node.js API, a database, a driver-facing React Native app, and a dispatcher web dashboard — is really four separate client surfaces around one shared backend, all maintained by one person. Treating each surface as its own project meant duplicated logic and drift almost immediately.
Context #
Each client has a genuinely different job (extract data, dispatch, drive, view), which made it tempting to build each one's business logic locally, close to where it was needed, rather than centralizing it.
What I Tried #
Let order-status transition logic live partly in the extension (marking orders as picked up), partly in the mobile app (marking them delivered), and partly in the dashboard (manual overrides) — each surface implementing its own version of "what's a valid status transition."
What Went Wrong #
The three implementations drifted: the mobile app allowed a transition the API didn't validate, which meant a delivery could reach a state the dashboard's own logic didn't expect and rendered incorrectly.
The Solution #
Moved all status-transition rules into the API as the single source of truth, and made every client — extension, mobile app, dashboard — a thin layer that calls the API and renders its response, rather than independently deciding what's valid.
// API owns the rule; every client just calls this and trusts the result
function canTransition(from: OrderStatus, to: OrderStatus): boolean {
const validTransitions: Record<OrderStatus, OrderStatus[]> = {
pending: ["assigned"],
assigned: ["out_for_delivery"],
out_for_delivery: ["completed", "failed"],
completed: [],
failed: ["assigned"],
};
return validTransitions[from]?.includes(to) ?? false;
}Why It Works #
With one person building four clients, the only way business logic doesn't drift is if there's exactly one place it's allowed to live. Clients that only render API responses can't disagree with each other about what's valid, because none of them are deciding that independently anymore.
Lessons Learned #
"Solo developer" doesn't mean fewer architectural constraints — it means the constraints have to be structural, because there's no code review from a second person to catch three slightly different versions of the same business rule.
What I Would Do Differently #
I'd design the API as the sole owner of business rules before writing the first client, instead of letting each client accumulate its own logic and later having to hunt down where they'd diverged.
Related Concepts #
Single source of truth for business logic, thin-client architecture, state machine modeling for order status.
Related content
Building a POS Integration Without Controlling the POS
TIL that integrating with a system you don't own means designing for its changes, not just its current behavior — the architecture that survived was the one that assumed the POS would break the integration eventually.
Designing One API for Both a Web Dashboard and a Mobile App
TIL that a REST API originally shaped around the web dashboard's screens needed real redesign, not just new endpoints, once a React Native app started consuming it too.
Serving Premium Downloads Without Exposing the File's Real Storage URL
TIL how to replace a guessable direct-download link with a permission-checked, time-limited presigned URL, so the object storage path a purchased asset actually lives at is never sent to the client.