FHIR for backend developers who were just told "we need FHIR"
Resources, references, profiles, and the three things that will surprise you in the first week.
Contents
Since three years I spend most of my working time between hospital systems, and the most common message I get from other developers is some version of: "the customer says we need FHIR, what is it and how bad is it?" So here is the introduction I wish someone had given me. It is not complete. It is the part you need in the first two weeks.
What FHIR actually is
FHIR (Fast Healthcare Interoperability Resources, pronounced "fire") is a standard from HL7 for exchanging healthcare data. In practice it is three things together:
- a data model: around 150 resource types like
Patient,Encounter,Observation,MedicationRequest,Condition - a REST API convention:
GET [base]/Patient/123,POST [base]/Observation, search with query parameters - a way to constrain the model for a country or a use case, called profiles
If you have built a JSON REST API before, the first contact feels friendly. That is intended, and it is also a little bit a trap.
Resources are small on purpose
A blood pressure measurement is not a field on the patient. It is an Observation with a code (what was measured, usually a LOINC code), a subject (a reference to the Patient), an effectiveDateTime and two components for systolic and diastolic. A lab result is an Observation too. So is a body weight. You will have many small resources pointing at each other, not one big document.
{
"resourceType": "Observation",
"status": "final",
"code": { "coding": [{ "system": "http://loinc.org", "code": "85354-9" }] },
"subject": { "reference": "Patient/123" },
"effectiveDateTime": "2026-07-21T08:15:00+02:00",
"component": [
{ "code": { "coding": [{ "system": "http://loinc.org", "code": "8480-6" }] },
"valueQuantity": { "value": 128, "unit": "mmHg" } },
{ "code": { "coding": [{ "system": "http://loinc.org", "code": "8462-4" }] },
"valueQuantity": { "value": 84, "unit": "mmHg" } }
]
}
References, not joins
Resources link to each other with reference strings like "Patient/123". There are no joins for you on the server side, but two search features replace most of them: _include (give me the Observations and include the Patients they reference) and _revinclude (give me the Patient and everything of type X that points to it). Learn these early. Otherwise you write N+1 calls against a hospital server that has a rate limit and a very tired administrator.
Search results come back as a Bundle of type searchset, paginated with a next link. Always follow the next link and do not build page URLs yourself, servers are allowed to use opaque cursors.
Profiles: the part that surprises everybody
Base FHIR is deliberately loose. Almost everything is optional and many elements allow several data types (value[x] can be a Quantity, a string, a CodeableConcept and more). Real projects therefore use profiles which say "in this country, a Patient must have an identifier of this system and a birth date". In the US that is US Core, for German hospitals it is ISiK, and almost every national program has its own. When a customer says "we need FHIR", the correct answer is "which profiles, which FHIR version, and which interactions". Most of the work hides in that answer.
Validate against the profiles in your CI. The official HL7 validator is a Java jar, it is slow and it is honest. Run it on your example payloads on every build and you will catch most problems before a hospital IT department catches them for you.
Three things that will surprise you in the first week
- Versions are not compatible. R4 is what most production systems speak today. R4B and R5 exist and change some resources in ways that matter. Ask, and write the version in your configuration, not in your head.
- Codes are everything. The same lab value can arrive with a local code, a LOINC code, or both. Never compare display texts. Build a small terminology mapping table from the beginning, it will grow anyway.
- Extensions are normal. Anything the base model does not cover comes as an
extensionwith a URL as its identifier. Your parser must keep unknown extensions and not drop them silently, otherwise you lose data on a round trip and nobody notices until a clinician does.
What I would do on day one
Start a FHIR test server locally (HAPI FHIR runs in Docker in a few minutes), load synthetic patients (Synthea generates realistic fake records, never use real ones for testing), and write the five searches your feature needs. Then read the profile for your country with a coffee. It is dry reading. It saves weeks.
If there is interest I can write about HL7 v2 next, which is older, uglier, and still carries most of the messages inside a typical hospital.
Written by
Hannah Weiss
Clinical informatics engineer in Berlin. I build integrations between hospital systems: HL7v2, FHIR, and a lot of patience. Formerly a nurse, which explains my opinions about alert fatigue.
Get the best of #vitals_and_variables weekly
One email each Monday with the most useful posts. No spam; unsubscribe in one click.
Comments
Sign in to join the conversation.
Sign in to comment