AnpherosAnpheros PlatformDevelopers

Personal health record (PHR) app with FHIR

Tutorial: the backend of a connected PHR on FHIR R4 — data from people, labs, documents and devices, a timeline, an IPS to share, consent. Tested code.

A personal health record (PHR) app keeps one person's health information — results, measurements, diagnoses, medications, documents — in a record the person controls, gathered from many sources rather than from one hospital. The hard parts are not the screens: they are getting data in from labs, clinics and devices in a usable form, keeping track of where each item came from, and letting the person share the record without losing control of it. This tutorial builds the backend of a PHR on HL7 FHIR R4 with the Anpheros TypeScript SDK; the code below was run against a local instance of the platform with a sandbox key.

Tethered, untethered and connected PHRs

Kind Where the data lives Limitation
Tethered (a hospital's patient portal) The provider's EHR Shows only that provider's data; changes when the person changes doctors
Untethered (a standalone app or notebook) The app's own database The person types everything; data rarely leaves the app
Connected A standard record that apps, labs and clinics read and write with the person's consent Needs standards, consent and provenance underneath

A connected PHR is the one worth building today. It is also what policy is moving towards: in the United States, certified EHRs must offer patients FHIR-based API access to their data (US Core), and in the European Union the European Health Data Space gives people the right to access their electronic health data and to share it with providers of their choice, phased in from 2029.

Where a PHR's data comes from

  1. The person: home measurements, symptoms, medications they take, conditions they know about. Mark them as patient-reported.
  2. Lab reports: most people receive PDFs; some labs can send HL7 v2 or CSV. Each result should become a coded Observation (Lab results API).
  3. Documents: discharge letters, imaging reports, prescriptions. Keep the original file, unchanged, next to the structured data.
  4. Devices and wearables: daily steps, heart rate, sleep, blood pressure from Apple Health or Health Connect (wearable data in FHIR).
  5. Providers: in the US, patient-access FHIR APIs of hospitals and insurers, reached with SMART on FHIR; elsewhere, mostly documents and summaries such as the International Patient Summary.

Each source writes into the same record. What keeps it trustworthy is that every item records who wrote it and from which system (medical data provenance).

The architecture

 person ──► your PHR app (mobile / web) ──► your backend ──► Anpheros: one FHIR R4 record per person
                                               │                 ├─ Observations, Conditions, Medications, Documents…
   lab report, PDF, device data ───────────────┘                 ├─ Provenance on every write
                                                                 ├─ access log the person can see
   other apps, doctors, AI assistants ◄── consent (OAuth) ───────┴─ $summary (IPS) to share

Your backend holds the project key (sk_test_… in the sandbox, never in the app). Your app signs people in with your own authentication and talks to your backend; the backend maps each user to one patient record.

Step by step

Install the SDK (npm install @anpheros/sdk) and get a free sandbox key from the dashboard. The sample uses a two-result HL7 v2 lipid panel (lipid-panel.hl7) and any PDF (report.pdf).

import { readFile } from 'node:fs/promises';
import { Anpheros, apiKey } from '@anpheros/sdk';

const anpheros = new Anpheros({ auth: apiKey(process.env.ANPHEROS_KEY!) });

// 1. One record per person who signs up in your PHR app
const jane = await anpheros.patients.create({ given: 'Jane', family: 'Doe', birth_date: '1984-07-19', gender: 'female' });

// 2. Something the person measured at home
await anpheros.observations.create(jane.id, {
  code: '85354-9', category: 'vital-signs', effective_at: '2026-10-01T08:30:00-05:00', author_type: 'patient',
  components: [{ code: '8480-6', value: 128, unit: 'mm[Hg]' }, { code: '8462-4', value: 82, unit: 'mm[Hg]' }],
});

// 3. A diagnosis and a medication the person reports
await anpheros.conditions.create(jane.id, { code: 'E11.9', system: 'http://hl7.org/fhir/sid/icd-10',
  display: 'Type 2 diabetes mellitus without complications', clinical_status: 'active', onset: '2021-03-15', author_type: 'patient' });
await anpheros.medications.create(jane.id, { code: 'A10BA02', display: 'Metformin 500 mg', status: 'active',
  dosage: '500 mg twice a day', start: '2021-03-20', author_type: 'patient' });

// 4. A lab report the person received, as the lab sent it (HL7 v2)
const lab = await anpheros.labs.importHl7(jane.id, await readFile('lipid-panel.hl7', 'utf8'), { sourceSystem: 'springfield-lis' });

// 5. A PDF they want to keep with the record
const doc = await anpheros.documents.upload(jane.id,
  { title: 'Chest X-ray report', kind: 'imaging_report', content_type: 'application/pdf', date: '2026-10-03' },
  await readFile('report.pdf'));

// 6. One timeline for the home screen
const timeline = await anpheros.timeline.list(jane.id, { limit: 10 });

// 7. A summary to share with a new doctor: the International Patient Summary
const ips = await anpheros.fhir.summary(jane.id);

// 8. Who else can read this record (empty until the person consents to another app)
const grants = await anpheros.grants.of(jane.id);

Printed from that run:

lab:       created 2, unmapped []
document:  final
timeline:  2026-10-03  document    Chest X-ray report
           2026-10-02  observation Cholesterol in HDL [Mass/volume] in Serum or Plasma
           2026-10-02  observation Cholesterol [Mass/volume] in Serum or Plasma
           2026-10-01  observation Blood pressure panel
           2021-03-20  medication  Metformin 500 mg
           2021-03-15  condition   Type 2 diabetes mellitus without complications
IPS:       Allergies, Medications, Problems, Results, Vital signs
grants:    0

Everything above is stored as FHIR R4 — Patient, Observation, Condition, MedicationStatement, DocumentReference — so the same record can be read with any FHIR client (GET /fhir/R4/Patient/{id}/$everything) and moved elsewhere intact. The /v1 calls in the SDK are a simpler view of the same resources; Medical data API lists them.

Sharing without losing control

A PHR exists to be shared, on the person's terms:

Families

Many PHRs are kept by one person for several: children, a partner, an elderly parent. Give each person their own record and link them to the guardian, rather than mixing everyone's data in one. Consent is then given per record. Patient medical record describes the model.

What to build yourself, and what not to

You build the experience: onboarding, how results are explained, reminders, charts, the language the person reads. Anpheros provides the record, the standard formats, consent, provenance, the access log and the lab, document and summary operations.

What Anpheros does not do for a PHR today: it does not log in to hospital patient portals or US patient-access APIs on the person's behalf (your app can do that with a SMART on FHIR client and write what it receives), it does not read PDF reports through the API, and it does not give medical advice. Anpheros' own PHR app, Anpheros Daily, is built on this same API.

Frequently asked questions

What is a personal health record (PHR)?

A record of a person's health information that the person keeps and controls, gathered from several sources — their own entries, labs, clinics, devices — rather than kept by a single provider.

What is the difference between a PHR and an EHR?

An EHR is kept by a healthcare organisation and covers what its providers recorded. A PHR is kept by the person and can combine data from all their providers and devices. A tethered PHR is a window into one provider's EHR; a connected PHR uses standards to bring several sources together.

Should a PHR use FHIR?

If the data is ever going to be shared with a doctor, another app or an AI assistant, yes: FHIR R4 gives coded, versioned resources that other systems already understand, and it is the format of patient-access APIs in the US and of the European patient summary.

How does a PHR app get data from hospitals?

In the US, through the FHIR patient-access APIs that certified EHRs and many insurers expose, using SMART on FHIR to let the person authorise the app. Elsewhere it is mostly documents and summaries, imported and stored next to structured data.

Where should a PHR app keep API keys?

On your server. A project key can read every record your project created, so it must never ship inside a mobile or web app; the app talks to your backend, and other people's apps use OAuth with the person's consent.

Related