FHIR data that updates itself.

FHIR is the standard way healthcare software shares patient data. ReFHIR is an open-source FHIR server that keeps your query results live: ask once, and the new answer arrives whenever the data changes.

Get started See it work

node watch.mjs
  1. 0 active: - The watcher starts and asks once.
  2. 1 active: Ann Ann is created.
  3. 2 active: Ann, Bee Bee is created.
  4. 1 active: Bee Ann is deactivated, so she leaves the result.
  5. 3 active: Bee, Cho, Dee Cho and Dee are created in one transaction, and arrive as one step.
A live query printing a new line each time the answer changes. Output from the tutorial, with no polling in the code.

The problem

Data changes. Your screen should too.

Try it

Change the record. Watch both screens.

A plain FHIR query

GET /MedicationRequest?patient=Patient/123&status=active
  • Metformin 500 mg
  • Lisinopril 10 mg
  • Atorvastatin 20 mg

True when it was fetched.

A ReFHIR live query

the same query, kept open
  • Metformin 500 mg
  • Lisinopril 10 mg
  • Atorvastatin 20 mg

Kept current by the server.

Both screens start with the same list.

Compare

Queries, not events.

An event says that something changed. A live query says what the answer is now, pushed over one WebSocket.

What a plain query, a Subscription and a live query each receive as the same things happen
What happens Plain queryask again SubscriptionFHIR’s notifications: one message per record Live querythe whole answer
You ask 0 active subscribed 0 active
Ann is created nothing Ann 1 active: Ann
Bee is created nothing Bee 2 active: Ann, Bee
Ann leaves the list nothing nothing sent 1 active: Bee
Cho and Dee, one transaction nothing ChoDee 3 active: Bee, Cho, Dee

Live queries and Subscriptions in depth

How it works

A write wakes only the queries it can change.

  1. A write commits
  2. Only queries that read it wake
  3. Each gets the whole new answer

PUT /MedicationRequest/456 commits

Observation?patient=Patient/123 Not touched
MedicationRequest?patient=Patient/123&status=active Re-runs. New answer pushed.
Encounter?patient=Patient/123&status=in-progress Not touched
Patient?name=ann Not touched

Run it

Run it. Watch it.

1Start a server

git clone https://github.com/hai-hq/refhir.git
cd refhir
docker compose up --build

2Change data. Watch it print.

0 active: -
1 active: Ann
2 active: Ann, Bee
1 active: Bee
3 active: Bee, Cho, Dee

Full tutorial

3Use it from your code

import { dial } from "@haihq/refhir";

const client = await dial("http://127.0.0.1:8080");
const watch = await client.search("Patient", { active: "true" });

console.log(watch.latest());      // the current matches
console.log(await watch.next());  // the next committed result
import { useFHIRQuery } from "@haihq/refhir/react";

function PatientList() {
  const { data, loading, error } = useFHIRQuery({
    baseURL: "http://127.0.0.1:8080",
    resourceType: "Patient",
    params: { active: "true" },
    runtime: "browser",
  });
  if (loading) return null;
  if (error) return <pre>{error.message}</pre>;
  return <pre>{JSON.stringify(data, null, 2)}</pre>;
}
package main

import (
	"context"
	"encoding/json"
	"log"

	"github.com/hai-hq/refhir/clients/go"
)

func main() {
	ctx := context.Background()
	client, err := refhir.Dial(ctx, "http://127.0.0.1:8080")
	if err != nil {
		log.Fatal(err)
	}
	defer client.Close()

	patient := json.RawMessage(`{
		"resourceType": "Patient",
		"active": true,
		"name": [{"family": "Ann"}]
	}`)
	_, err = client.Create(ctx, "Patient", patient)
	if err != nil {
		log.Fatal(err)
	}

	watch, err := client.Search(ctx, "Patient", map[string]string{"active": "true"})
	if err != nil {
		log.Fatal(err)
	}
	defer watch.Close()

	log.Printf("current: %s", watch.Latest())

	// Blocks until another client (or this one) commits a change that affects the search.
	updated, err := watch.Next(ctx)
	if err != nil {
		log.Fatal(err)
	}
	log.Printf("updated: %s", updated)
}
GET /Observation?patient=Patient/123&status=final        <- a snapshot, as with any FHIR server
ws://host/sync   subscribe: Observation?patient=Patient/123&status=final
                 <- the matches now, then a new result whenever a commit changes them

For agents

Live context for AI agents.

  1. 1 Read a live query keeps it current
  2. 2 Reason seconds or minutes pass
  3. 3 Check did the result change?
  4. 4 Write refused if the record moved
agent reads 1 active medication(s) and starts reasoning
context changed: now 0 active medication(s), so the agent re-plans
write refused: If-Match 12 does not match version 13

What is inside

A real FHIR server underneath.

  • FHIR R4
  • Search
  • Transactions
  • Bulk
  • GraphQL
  • Subscriptions
  • SMART sign-in
  • Every version kept
  • Validation
  • SQLite or Postgres
  • Docker, Helm, binary
  • Standby and replicas
  • Audit trail

The FHIR API Deployment guide Strict defaults to know about

Ask once. Stay current.

Get started View on GitHub

Open source under Apache-2.0, from HAIHQ, a nonprofit. Contribute Open an issue Report a vulnerability