← Back to all work
(02 — 02)

Full pharmacy operations built for infrastructure you can't control

DesktopElectronReactOffline-first
Client
mPharma
Role
Software Engineer
Timeline
Ongoing
Platform
macOS / Windows / Linux / Web
Bloom Desktop pharmacy point-of-sale interface
The problem

Pharmacy operations don't pause when the internet does

mPharma's pharmacy network spans multiple African markets where connectivity is unpredictable by design, not by accident. The existing workflows assumed a live server connection for every transaction — sales, patient records, inventory lookups — which meant a dropped connection didn't just slow things down, it stopped the pharmacy entirely.

The replacement needed to work fully offline, support hardware setups ranging from single-workstation clinics to multi-branch chains, and run as either a native desktop app or a browser tab depending on what the site had available — without maintaining two separate codebases.

The approach

Local database as the source of truth, the server as the eventual destination

We built a platform adapter layer so the same React codebase could target Electron or the browser without knowing the difference. In Electron, every database operation crosses a typed IPC bridge to a Prisma-backed main process — the UI never touches the database directly. In the browser, Dexie over IndexedDB implements the same adapter contract.

The database layer supports both SQLite for standalone installs and PostgreSQL for pharmacy chains, each with its own Prisma schema and generated client. The type differences between the two are real enough that a single shared schema causes subtle bugs — two honest schemas is the cleaner call. All configuration is stored encrypted on disk using the OS keychain via Electron's safeStorage API.

The sync engine runs in the background whenever connectivity is available. Unsynced sales and patient records queue locally, sync in dependency order — patients before sales — and handle the awkward edge cases. A request that timed out but actually reached the server gets detected by its UUID, deduplicated, and retired rather than creating a duplicate record or surfacing a false error.

A pharmacy POS that loses a sale because of a timeout isn't an offline-first app — it's an online app with a worse failure mode.
The outcome

One build, every environment, no data left behind

Pharmacists can process sales, register patients, check inventory, and print receipts whether the router is up or not. When connectivity returns, the sync engine catches up without any manual intervention and surfaces a clear sync status so staff always know where their data stands.

The same codebase ships to macOS, Windows, and Linux, and serves as a browser app for sites that can't install native software. The SQLite-to-PostgreSQL path is built into the database adapter, so a single-location pharmacy can grow into a chain without a migration project or a rebuild.

By the numbers
2
DB backends, one adapter contract
OS targets from one build
0
Network required to sell
Stack & tools
ElectronReact 19TypeScriptPrismaSQLitePostgreSQLViteMUIDexieRedux Toolkit
Next project

A booking platform that actually survives bad network days

View case study →