Quick Start
Under five minutes from install to a typed response. Uses the public HAPI test server at https://hapi.fhir.org/baseR4.
1. Install and generate
npm install @fhir-dsl/core @fhir-dsl/runtime
npm install -D @fhir-dsl/cli
npx @fhir-dsl/cli generate --version r4 --ig hl7.fhir.us.core@6.1.0 --out ./src/fhir
The last line writes ./src/fhir/r4/ with client.ts, resources/*.ts, profiles/*.ts, search-params/*.ts, and (if --validator was passed) schemas/*.ts.
2. Create a client
// src/fhir.ts
import { createClient } from "./fhir/r4";
export const fhir = createClient({
baseUrl: "https://hapi.fhir.org/baseR4",
// auth: { type: "bearer", credentials: process.env.FHIR_TOKEN! },
});
3. First typed search
import { fhir } from "./fhir";
const result = await fhir
.search("Patient")
.where("family", "eq", "Smith")
.where("birthdate", "ge", "1990-01-01")
.sort("birthdate", "desc")
.count(5)
.execute();
// Expected shape (typed, not just guessed):
// result: {
// data: Patient[];
// total?: number;
// included: never[]; // no .include() called
// link?: BundleLink[];
// raw: unknown; // the raw Bundle response
// }
for (const p of result.data) {
console.log(p.id, p.name?.[0]?.family, p.birthDate);
}
Every argument is narrowed:
"Patient"is constrained to resource types in the generated schema."family"autocompletes from the Patient search params."eq"is valid becausefamilyis astringparam;"gt"would be a type error.result.data[0].birthDateisFhirDate | undefined, notany.
4. One advanced query
Cross-reference search with .whereChained plus _include to pull related resources in a single request:
import { fhir } from "./fhir";
// Observations whose subject is a Patient named "Smith",
// with the Patient and the performing Practitioner included in the bundle.
const obs = await fhir
.search("Observation")
.whereChained(["subject", "Patient"], "family", "eq", "Smith")
.where("status", "eq", "final")
.include("subject")
.include("performer")
.count(20)
.execute();
// obs.data is Observation[]
// obs.included is typed as (Patient | Practitioner | PractitionerRole | Organization | ...)[]
// — the exact union comes from the generated `includes` map.
for (const o of obs.data) {
console.log(o.code.coding?.[0]?.display, o.valueQuantity?.value, o.valueQuantity?.unit);
}
for (const inc of obs.included ?? []) {
if (inc.resourceType === "Patient") console.log("patient:", inc.id);
}
The compiled URL (visible via .compile() instead of .execute()) is:
GET Observation?subject:Patient.family=Smith&status=final&_include=Observation:subject&_include=Observation:performer&_count=20
Next steps
- Core Concepts — how the 6-generic
SearchQueryBuilderthreads schema, profile, selection, and include-union through every chained call. - DSL Syntax Reference — every method:
whereIn,whereMissing,whereComposite,has,withProfile,select,summary,total,usePost,$if/$call. - CLI Usage — every generator flag, output layout, and IG conventions.
- Validation Guide — wiring zod or native Standard Schema validators into
.validate().