Every healthcare app hits the same fork early: invent your own database schema, or model your clinical data as FHIR. The default instinct — "I'll just make a notes table and a patients table" — feels faster. It usually isn't. Here's how to make the call deliberately instead of by accident.
Should you build your healthcare app on FHIR?
Yes, in almost every case. If your app stores clinical data — patients, notes, vitals, assessments, diagnoses — model it as FHIR R4 from day one. You get interoperability, portability, and AI-agent readiness for free, and you avoid rebuilding a worse version of a 150-resource standard yourself. Skip FHIR only for pure-consumer wellness tools that never touch a clinical system.
The custom-schema trap: you'll rebuild FHIR, badly
The seductive shortcut is to design your own tables. It feels lighter. But clinical data is deceptively deep, and you discover the depth one painful migration at a time:
- A "note" isn't a text column. It has an author, a status (draft → signed → amended), an encounter it belongs to, a patient, a type, and a timestamp that is not the row's
created_at. - A blood-pressure reading is two numbers, two units, a body site, a method, a "who measured it," and a coded concept — not
bp_systolic INT. - A diagnosis needs a coding system, a code, a clinical status, a verification status, and an onset that might be a date, an age, or "childhood."
- An allergy has a criticality, a reaction, a severity, and a "refuted vs. confirmed" state your
boolean has_allergycan't hold.
Each of these is a FHIR resource that a standards committee already argued about for years. When you roll your own, you re-litigate every one of those decisions under deadline pressure — and you get them subtly wrong. Six months in, your "simple" schema has accreted a status enum, a versioning hack, a coded-value side table, and a soft-delete column, and you've built FHIR, badly, without the ecosystem. The standard exists precisely so you don't have to.
FHIR is the Series-A expectation
If you plan to raise, get acquired, or sell into a health system, your data model becomes due-diligence material. The questions are predictable: "Can you export a patient's record?" "Can you integrate with an EHR?" "Are you ready for the US interoperability rules?" "How locked-in is your data?" "It's standard FHIR" answers all of them in one sentence. "We have a proprietary schema, but we could write an exporter" is a red flag that turns into a re-platforming line item. Building on FHIR isn't gold-plating — it's the table-stakes answer to questions you will be asked.
FHIR makes you AI-agent-ready
This is the newest and most underrated reason. Agents — scribes, chart-summarizers, prior-auth bots — reason far better over a typed, well-known schema than over your bespoke tables. FHIR resource types are in every model's training data; Observation, Condition, and MedicationRequest carry meaning a model already understands. A custom data_blob JSONB column carries none.
The wedge we care about most is the AI medical scribe. A scribe's whole job is to turn a conversation into structured clinical facts. If those facts land as FHIR, your agent can prep, write, and reconcile against a known shape — and your export, your audit trail, and your interop all come along for free:
// The scribe pipeline, on FHIR primitives
const prep = await clinical.agent.sessionPrep({ patientId });
// → last visit, open problems, active meds, due assessments
await clinical.notes.create({
patientId,
text: draftFromTranscript, // SOAP/DAP, signed by the clinician
});
await clinical.observations.record({
patientId,
code: "8480-6", // Systolic BP (LOINC)
value: 128, unit: "mm[Hg]",
});
await clinical.assessments.record("PHQ-9", { patientId, score: 11 }); Under the hood each call writes a FHIR resource — a DocumentReference for the note, an Observation for the vital, a scored QuestionnaireResponse for the PHQ-9. Your agent reasons over standard types; your data stays exportable. (More on the scribe pattern on the AI scribe page.)
What you get for free
Choosing FHIR isn't just defense. It unlocks a real ecosystem you'd otherwise build from scratch:
- SMART-on-FHIR — the standard OAuth flow to launch your app from inside an EHR, with scoped access. Build to FHIR and you're launch-ready.
- Apple Health & device data — Apple's HealthKit exports as FHIR. Speak FHIR and patient-generated data flows in without a custom adapter.
- HIEs and EHRs — health information exchanges and EHRs speak FHIR. Interop becomes a configuration problem, not a rebuild.
- Validators, terminology, and tooling — public FHIR validators, US Core profiles, and code systems like SNOMED CT (free for US use via the NLM UMLS license), LOINC, and RxNorm. You inherit shared meaning instead of inventing it.
- A clean export — when an enterprise asks for the patient's record,
clinical.fhir.export(patientId)returns a standard Bundle. No exporter project.
When you should NOT use FHIR
An honest guide has to draw the other line too. FHIR is overhead, and sometimes the overhead isn't worth it:
- Pure consumer wellness with no clinical interop. A meditation timer, a habit tracker, a fitness-streak app that never exchanges data with a provider — modeling a heart-rate log as a FHIR
Observationbuys you nothing. A plain column is correct. - Throwaway prototypes and internal tools. If you're validating a workflow this week and will rewrite the data layer anyway, don't model FHIR yet. Just don't let the prototype quietly become production.
- Data that genuinely isn't clinical. Billing line items, scheduling preferences, UI state, feature flags, app analytics — these aren't health resources and shouldn't be forced into FHIR. Keep them in normal app tables alongside your clinical layer.
- Operational scratch state. "Needs review" flags, draft autosave, assignment queues — FHIR has no good home for these, and that's fine. They belong in app state, not the clinical record.
Notice the pattern: skip FHIR for the parts of your app that are not the clinical record. The mistake is the inverse — building the clinical record on a custom schema to "move fast," then paying for it at the worst possible time.
"Build on FHIR" doesn't mean "build directly on a FHIR server"
Here's the nuance that trips teams up. FHIR is the right data model — but a raw FHIR server (HAPI, Aidbox, AWS HealthLake, and others) is an interoperability engine, not an application backend. Build your UI straight against one and you inherit eventually-consistent reads, no home for app state, no built-in access control, and a terminology server to babysit. We cover that gap honestly in FHIR, explained for app developers, and we compare the FHIR servers head-to-head in our backend comparisons.
The pattern that works: FHIR underneath, app-native primitives on top. Your code says "create a note for this patient" and "give me this patient's timeline," with fresh-on-commit reads, real per-patient authorization, and a query you'd actually want — while FHIR R4 resources are generated underneath so you can still export a Bundle and talk to an EHR. The data your app reads stays live and queryable:
// App-native read, FHIR underneath, fresh on commit
const { data: timeline } = useClinicalQuery(
clinical.notes.list({ patientId, status: "signed" })
); That's the whole idea behind a clinical backend like bonfireDB's FHIR layer (open-source core, with a managed BAA option, in early access): FHIR is the foundation and the export format, but you don't write FHIR JSON by hand to ship a list screen.
Keep reading
TL;DR
- Build the clinical record on FHIR. A custom schema doesn't dodge the complexity — it defers it into a painful migration.
- It's the Series-A expectation. "It's standard FHIR" answers interop, portability, and lock-in due-diligence in one sentence.
- It makes you AI-agent-ready. Agents reason over standard resource types; your scribe writes structured, exportable facts.
- You get SMART, Apple Health, HIEs, validators, and SNOMED/LOINC for free.
- Skip FHIR for non-clinical parts — pure wellness, throwaway prototypes, billing, scheduling, UI/app state.
- "On FHIR" ≠ "directly on a FHIR server." Use FHIR as the model and export layer; build your app against app-native primitives on top.