FHIR, explained for app developers

No jargon. What FHIR actually is, why it exists, the handful of concepts you need — and the honest reason building your app directly on a FHIR server hurts.

If you're a developer or a clinician-founder about to build a healthcare app, you'll hit three letters fast: FHIR. People say it like you should already know it, the docs are thousands of pages, and somehow everything is both "a standard" and "a server." Here's the version that actually helps you ship.

What is FHIR, really?

FHIR (Fast Healthcare Interoperability Resources, pronounced "fire") is a standard for representing and exchanging health data over a normal REST API. That's it. Instead of every hospital inventing its own "patient" format, FHIR defines ~150 typed resourcesPatient, Observation, Encounter, MedicationRequest, DocumentReference — as JSON, and a standard way to read and write them: GET [base]/Patient/123, POST [base]/Observation, GET [base]/Observation?patient=123&code=....

Think of it as "the Stripe API, but for clinical data, designed by a standards committee instead of one company." When someone says "a FHIR server" (HAPI, AWS HealthLake, Medplum, Aidbox), they mean a database that speaks this API.

Why does FHIR exist?

One reason: interoperability between organizations that don't trust each other and don't share a database. A hospital, a lab, a payer, and a startup all need to move a patient's record between them without a custom integration for every pair. FHIR is the shared language that makes that possible — and increasingly it's required (regulations in the US push providers and payers toward FHIR APIs).

Keep that origin in mind, because it explains everything good and everything painful about FHIR.

The concepts you actually need

  • Resources — typed JSON objects (Patient, Observation…). Your data is a graph of these.
  • References — resources point at each other (an Observation references its Patient and Encounter). You resolve the graph yourself.
  • Search — a query language over resources (?patient=, ?code=, ?date=ge2026-01-01), plus power tools like _include and chained search.
  • Profiles / US Core — constraints layered on base resources for a use case (e.g. US Core says which fields a US patient record must have). A second large spec on top of the first.
  • Terminology — coded fields use external vocabularies: LOINC for labs, SNOMED CT for clinical terms, RxNorm for meds, ICD-10 for diagnoses.
  • SMART-on-FHIR — the OAuth flow for launching apps from an EHR and scoping access.

That's 90% of what you need to read a FHIR conversation without drowning.

Why FHIR is genuinely good

If you store clinical data as FHIR, you get real benefits: your data is portable (not locked in a proprietary schema), interoperable (it can talk to EHRs and other systems later), and you inherit a huge ecosystem of tools, validators, and shared meaning. For anything that might one day exchange data with the broader health system, FHIR is the right target. Don't fight it.

So why does building an app directly on a FHIR server hurt?

Here's the part nobody tells you up front. FHIR was designed to move records between systems — not for one team building one product. The moment you build an app on it, you inherit the operating burden of a federation protocol when all you wanted was a typed read/write API and a list screen. Concretely, builders keep hitting the same walls:

  • Your reads are "eventually consistent." Save a note, then ask for "this patient's notes," and it may not show up for 15–30 seconds while the search index catches up. For a UI, that's a bug.
  • There's nowhere for app state. Drafts, "needs review" flags, UI status — FHIR has no home for them, so teams stand up a second database and inherit sync hell. And you can't query by arbitrary fields, joins, or aggregates, so even a simple list screen needs a hand-built projection.
  • FHIR is a data model, not an access-control system. It can record consent, but it doesn't enforce who-can-see-what. Per-patient authorization, minimum-necessary, and a real audit trail are on you.
  • Every coded field drags in a terminology server. "Just store a diagnosis" becomes loading SNOMED/LOINC, validating codes, and versioning vocabularies.
  • Concurrency is sharp. A full PUT replaces the whole resource (easy to clobber a teammate's edit), and idempotent "create if not exists" has a documented duplicate-record race.
  • AI agents struggle with raw FHIR. On the standard benchmark, the best agent answers real questions over raw FHIR correctly only about half the time — a single patient record can be ~3 million tokens of JSON.
None of these mean FHIR is wrong. They mean FHIR is an interoperability format, and you're trying to use it as an application backend. Those are different jobs.

FHIR vs a plain database — when each wins

The honest framing isn't "FHIR good" or "FHIR bad" — it's that a plain database and a FHIR server are good at opposite things. You usually want both jobs done, which is the whole argument for generating FHIR underneath an app-native layer rather than building on a bare server.

What you needPlain database vs FHIR server
Fresh-on-commit reads for a UIPlain DB wins — your write is immediately queryable; FHIR search is often eventually consistent.
Arbitrary queries, joins, aggregatesPlain DB wins — SQL does this natively; FHIR search can't express most of it.
App state (drafts, "needs review", UI status)Plain DB wins — FHIR has no home for app-only state.
Portability and clean exportFHIR wins — a standards-compliant Bundle other systems already understand.
Talking to EHRs, labs, and payersFHIR wins — it's the shared language those systems require.
Shared clinical meaning and validationFHIR wins — typed resources, profiles, and a terminology ecosystem.

So what should you actually do?

The pattern that works: keep FHIR as your interoperability and export layer, but build your app against app-native clinical primitives on top of it. Your code should say "create a note for this patient" and "give me this patient's timeline," with fresh-on-commit reads, real authorization, and built-in storage and search — while FHIR resources are generated underneath so you can still export a clean Bundle and talk to an EHR when you need to.

That's the whole idea behind a clinical backend like bonfireDB (open source, early access): the app-native layer above the FHIR server, so you build the workflow and don't hand-roll the clinical data layer. If you want the deeper version of the pains above, see why building on FHIR is hard; for the architecture, see how it works.

Keep reading

TL;DR

  • FHIR = a standard + REST API for clinical data, built for exchange between organizations.
  • Use it — it gives you portability, interop, and an ecosystem.
  • But don't build your app directly on a FHIR server: you'll fight eventual-consistency reads, no app-state home, no access control, terminology, and concurrency.
  • Build on app-native clinical primitives, with FHIR generated underneath.
FAQ

Frequently asked questions

What is FHIR, in plain English?

FHIR (Fast Healthcare Interoperability Resources, pronounced "fire") is a standard for representing and exchanging health data over a normal REST API. It defines about 150 typed resources — Patient, Observation, Encounter, MedicationRequest — as JSON, with a standard way to read and write them. Think of it as the Stripe API, but for clinical data.

Why does FHIR exist?

For interoperability between organizations that don't trust each other and don't share a database. A hospital, a lab, a payer, and a startup all need to move a patient's record without a custom integration for every pair. FHIR is the shared language that makes that possible, and US regulations increasingly require it.

What FHIR concepts do I actually need to know?

Six: resources (typed JSON objects), references (resources pointing at each other), search (a query language with includes and chaining), profiles like US Core (constraints for a use case), terminology (LOINC, SNOMED CT, RxNorm, ICD-10), and SMART-on-FHIR (the OAuth flow for launching apps from an EHR). That covers most FHIR conversations.

Why does building an app directly on a FHIR server hurt?

FHIR was designed to move records between systems, not to back one product. Build on it and you inherit eventually-consistent reads, no home for app state, no access-control enforcement, a terminology server for every coded field, and sharp concurrency. You wanted a typed read/write API and a list screen; you got a federation protocol.

Can AI agents query raw FHIR well?

Not reliably. On the standard benchmark (FHIR-AgentBench, arXiv 2509.19319), the best agent answers real questions over raw FHIR correctly only about half the time. A single patient record can be roughly three million tokens of JSON, so agents drown in payload before they can reason over the chart.

So what should I actually do — drop FHIR?

No. Keep FHIR as your interoperability and export layer, but build your app against app-native clinical primitives on top of it: fresh-on-commit reads, real authorization, built-in storage and search, with FHIR resources generated underneath. That's the idea behind bonfireDB — build the workflow, don't hand-roll the clinical data layer.

You build the app. Bonfire is the clinical data layer underneath.

bonfireDB gives you the app-native clinical backend, with FHIR underneath. Open source, in early access.