FHIR vs. a custom medical database
Your own tables or HL7 FHIR R4 for medical data: the same reading stored both ways, the trade-offs side by side, when each is right, and the usual split between your database and the FHIR record.
A team starting a health product usually faces the same choice early: design its own tables for patients, measurements and diagnoses in PostgreSQL or another database, or store medical data as HL7 FHIR R4 resources from the start. A custom schema is faster on day one and fits your product exactly. FHIR costs more thought up front and pays back every time the data has to leave your system, be audited, be shared with consent or be read by someone who is not you. This page sets out the trade-offs honestly, with the same reading stored both ways, and says when each choice is right.
This is a different question from which FHIR server to use. For that, see How Anpheros compares with other FHIR servers.
The same reading, two ways
A blood-pressure reading in a custom schema:
CREATE TABLE bp_readings (
id uuid PRIMARY KEY,
user_id uuid REFERENCES users(id),
systolic int,
diastolic int,
measured_at timestamptz,
created_at timestamptz DEFAULT now()
);
Simple, fast to query, exactly what the app needs. Then come the questions the table does not answer:
- Which device or person produced the value? Was it typed by the patient, measured by a cuff, or entered by a nurse?
- What was the value before someone corrected it, and who corrected it?
- How does a clinic system, a lab or an insurer read it? What does
systolicmean in their system? - How does the patient share it with their doctor's app and stop sharing it next month?
- When you add heart rate, weight, HbA1c and 200 other measurements, is that 200 tables or one generic table you design yourself?
The same reading as FHIR R4:
{"resourceType": "Observation", "status": "final",
"category": [{"coding": [{"system": "http://terminology.hl7.org/CodeSystem/observation-category", "code": "vital-signs"}]}],
"code": {"coding": [{"system": "http://loinc.org", "code": "85354-9", "display": "Blood pressure panel"}]},
"subject": {"reference": "Patient/d09e3942-…"}, "effectiveDateTime": "2026-10-10T08:30:00Z",
"component": [
{"code": {"coding": [{"system": "http://loinc.org", "code": "8480-6"}]}, "valueQuantity": {"value": 138, "unit": "mmHg", "system": "http://unitsofmeasure.org", "code": "mm[Hg]"}},
{"code": {"coding": [{"system": "http://loinc.org", "code": "8462-4"}]}, "valueQuantity": {"value": 88, "unit": "mmHg", "system": "http://unitsofmeasure.org", "code": "mm[Hg]"}}],
"meta": {"versionId": "1"}}
More verbose, but every part has a meaning any FHIR system shares: LOINC says what was measured, UCUM says the unit, meta.versionId starts a history, and the same Observation shape holds the other 200 measurements. With Anpheros, a Provenance resource also records who wrote it. How to store medical records using FHIR R4 walks through it.
Side by side
| Custom schema | FHIR R4 | |
|---|---|---|
| Time to the first feature | hours | a day or two to learn the resources |
| Fit to your product | exact | general; your product's view sits on top |
| Adding a new kind of data | a migration per kind | usually a new code in an existing resource |
| Meaning outside your system | none without a mapping | shared codes (LOINC, ICD-10, ATC, UCUM) |
| History and corrections | you design it | versions and _history by design |
| Who wrote what | you design it | Provenance (automatic in Anpheros) |
| Exchange with clinics, labs, other apps | custom integration each time | FHIR R4 API, HL7 v2 import, International Patient Summary |
| Patient consent per application | you design it | SMART on FHIR scopes and OAuth |
| Querying for analytics | plain SQL | FHIR search, or export to your warehouse |
| Regulation (GDPR Article 9, EHDS) | you document every part | standard formats the regulation refers to |
When a custom schema is the right call
- The data is not really medical, or not about one person. Training plans, content, a product catalogue: use your own tables.
- A short-lived prototype to test whether anyone wants the product, with synthetic data and a plan to move.
- Data no standard models well, for example a novel research measurement. FHIR can still carry it (
Observationwith your own code system, orBasic), but a custom table may be clearer. - Heavy analytics. Even with FHIR as the system of record, most teams copy data into a warehouse in their own shape for reporting.
When FHIR is the right call
- The data is a person's health record: measurements, diagnoses, medications, allergies, lab results, documents.
- It will ever leave your system: to the patient, a doctor, a clinic, a lab, an insurer, an AI model, or a competitor when the patient moves.
- You need to show who wrote what and what changed: audits, clinical safety, disputes.
- Patients will grant and revoke access per application.
- You sell in the European Union, where the European Health Data Space expects records to be exchangeable in standard formats.
The usual middle path
Most products end up with both, split by kind of data:
your database FHIR record (Anpheros)
───────────── ──────────────────────
users, sessions, settings Patient
subscriptions, billing Observation, Condition, MedicationStatement
product content, feature flags AllergyIntolerance, Immunization, DocumentReference
user id → patient id (one column) Provenance, Consent, history
caches and analytics copies ◄─────── webhooks
Your database stays small and product-shaped. The medical record is standard, versioned and shareable, and you did not have to build it. Medical app backend and database describes this split in detail, and How to build a healthcare API with TypeScript builds it.
What it takes to build FHIR yourself
Choosing FHIR does not mean writing a FHIR server. Your options are an open-source server you run, a cloud provider's FHIR store, an open-source healthcare platform, or a managed healthcare database like Anpheros. They differ in who handles consent, audit, provenance, residency and integrations, which How Anpheros compares with other FHIR servers sets out.
Frequently asked questions
Is FHIR a database?
No. FHIR is a standard for the structure and exchange of health data: resources, codes and a REST API. A FHIR server or managed FHIR database stores the resources and implements the API on top of a real database.
Can I store FHIR resources in PostgreSQL myself?
Yes, as JSON columns with your own indexes, and some open-source servers do exactly that. The work is in everything around storage: search parameters, versioning, references, validation, access control per patient and provenance.
Can I start with a custom schema and move to FHIR later?
You can, and many teams do, but the migration is a mapping project: every table needs codes, units and authors it never recorded. Storing medical data as FHIR from the first feature costs less than converting it later.
Do I need FHIR to comply with GDPR?
No. GDPR does not require a particular format. FHIR makes several obligations easier to meet: portability (the record exports in a standard format), access control per purpose, and showing who processed which data.