When we set out to build our hospital management system, we knew the engineering decisions made in the first six months would shape every feature we’d ship for years afterward. This post is a candid technical look at how we designed the system — the architecture choices, the data model trade-offs, the integration headaches, and the lessons we’d take into the next build.
This isn’t a marketing post. If you’re an engineer evaluating HMS platforms, planning to build one, or just curious about how clinical systems are constructed under the hood, this is for you.
The architectural starting point
We started with three non-negotiable requirements that shaped everything else:
- The system has to work during network outages. Hospitals can’t pause patient care because the internet went down.
- Patient data has to remain consistent across modules. A vitals reading entered in the ward must be immediately visible in the doctor’s view, the billing system, and the audit log.
- The system has to integrate with at least a dozen external systems — labs, pharmacies, insurance, government reporting, imaging, and so on.
Those three requirements ruled out several common architectural patterns immediately. A pure single-server monolith couldn’t meet the resilience requirement. A purely event-driven microservices architecture would make cross-module consistency painful. A pure cloud-only deployment would fail in hospitals with unreliable connectivity.
We settled on a modular monolith deployed in a hybrid topology: a core services layer running locally at the hospital with bi-directional sync to a cloud control plane.
The data model: getting patients right
The single most important design decision in any HMS is the patient data model. Get this wrong and every other feature suffers.
We chose to model patients with a strict separation between identity, demographics, and clinical data:
- Identity: a stable internal ID that never changes, plus a collection of external identifiers (national ID, insurance number, MRN, etc.) that can change over time
- Demographics: name, contact info, address, etc. — versioned, with full history
- Clinical data: encounters, observations, diagnoses, procedures, medications — modeled to align with FHIR resources from the start
The FHIR alignment was the most important decision. FHIR is the modern standard for healthcare data exchange, and modeling our internal entities to map cleanly to FHIR resources meant integrations later became dramatically easier. Every clinical concept in our system has a defined FHIR equivalent, so exporting to or importing from FHIR-compliant systems is largely mechanical.
We learned the hard way to use surrogate keys (auto-generated internal IDs) as the primary keys for every entity, never natural keys like national ID or MRN. Natural keys change more often than you expect, and you can’t change a primary key without breaking foreign keys everywhere.
Handling concurrency in clinical workflows
A hospital is a deeply concurrent environment. Multiple doctors and nurses are reading and writing to the same patient record simultaneously. A doctor updating a prescription while a nurse records vitals while a lab technician posts a result — this is the normal operating state, not the edge case.
We use optimistic concurrency control on most entities. Each record has a version number; updates require the version the client started from, and the database rejects writes if the version has moved on. The client then re-reads, merges intelligently where possible, or surfaces the conflict to the user.
For critical entities like medication orders, we use stricter pessimistic locks during the brief window of an order being signed. The performance cost is negligible and the safety benefit is significant.
The offline-first sync layer
The offline-first requirement was the hardest engineering problem in the project. The basic idea is straightforward — keep a local replica of the data each workstation needs, queue writes when the network is unavailable, and reconcile when it returns. The reality is full of edge cases.
We use a CRDT-inspired approach (Conflict-free Replicated Data Types) for fields where last-writer-wins is acceptable — things like demographic edits and free-text notes. For clinical events like vitals and medication administration, we use append-only logs that never need to be reconciled because adding a new entry is always safe.
The hardest case is anything involving a finite resource: bed assignments, OT scheduling, inventory. Two offline clients can’t both legitimately claim the last bed. For these, we accept that some operations must require connectivity, and we surface that limitation clearly in the UI rather than hiding it.
Integration: the unglamorous reality
Integration is where every HMS project either succeeds or quietly fails. The reality is that healthcare runs on a chaotic mix of standards (HL7 v2, FHIR, DICOM, CDA), proprietary formats, and one-off file dumps over SFTP.
We built an integration layer with three components:
- Adapters that translate external formats to and from our internal data model
- A message queue that decouples integration timing from user-facing operations
- An audit log that records every inbound and outbound message in its raw form for debugging and compliance
The single most useful design choice we made here was storing every external message in its original raw form, even after we’d parsed and processed it. Six months later, when an integration mysteriously stops working, having the raw messages to inspect saves days of debugging.
Security: layered, not bolted on
Healthcare security can’t be retrofitted. We designed in:
- Role-based access control at every API endpoint, with role definitions matched to hospital job roles
- Per-record access logging, with no exception for administrators (administrators get logged too)
- Field-level encryption for the most sensitive fields (national IDs, HIV status, mental health notes)
- Audit logs that are append-only and stored separately from operational data, so a breach of the operational database doesn’t compromise the audit trail
- Multi-factor authentication required for clinical users by default, with break-glass procedures for emergencies
We also adopted a strict principle that no patient data ever leaves the hospital’s primary deployment without explicit operator authorization. Analytics and reporting that need aggregated data work on de-identified extracts, never on raw production data.
What we’d do differently
If we were starting over, three things would change:
- Start with FHIR even more aggressively. We mapped to FHIR but kept some internal abstractions. A purer FHIR-native model would have been better long-term.
- Invest in observability from day one. We added structured logging, metrics, and tracing in year two. We should have had them on day one. Debugging distributed clinical workflows without proper observability is brutal.
- Build the integration test harness first. We built it after the integration layer matured, which meant every adapter was tested in production-like environments instead of automatically. Reversing that order would have caught dozens of bugs earlier.
Lessons that generalize
Three lessons from this project apply well beyond HMS:
- In high-stakes systems, prefer boring technology. Postgres beats exotic databases. REST beats novel protocols. Server-rendered HTML beats heavy SPAs where you can use it. The cost of bugs is too high to also be debugging your dependencies.
- Design for the worst day, not the average day. Hospitals have worst days. Software has to be at its strongest when the people using it are at their most stressed.
- The data model is the product. UI can be changed in a sprint. Data models, once production has years of data in them, are nearly immovable. Invest accordingly.
Building an HMS is a multi-year journey, and the technical decisions made early matter more than any feature shipped later. We’re still learning, but the foundation has held up well — and that’s mostly down to obsessing over the boring parts before the exciting ones.


