fhir-rest-client
v0.3.2
Published
FHIR R4 TypeScript HTTP SDK — zero runtime dependencies, cross-platform (Browser + Node.js 18+)
Maintainers
Readme
fhir-rest-client
A modern, type-safe FHIR R4 TypeScript HTTP client with zero runtime dependencies.
Features
- ✅ Zero Runtime Dependencies — No external packages required at runtime
- 🌐 Cross-Platform — Works in Browser and Node.js 18+
- 🔒 Type-Safe — Full TypeScript support with comprehensive type definitions
- 🔐 Multiple Auth Methods — Bearer token, Client credentials, Password flow, PKCE
- 💾 Smart Caching — LRU cache with TTL and automatic invalidation
- 🔄 Auto-Retry — Exponential backoff for transient failures
- 📦 Auto-Batching — Automatic request batching with configurable windows
- 🔍 Fluent Query Builder — Type-safe search parameter construction
- 🔌 WebSocket Subscriptions — Real-time resource updates with auto-reconnect
- 🪶 Lightweight — Tree-shakeable ESM and CJS builds
Installation
npm install fhir-rest-clientQuick Start
import { FhirClient } from "fhir-rest-client";
// Create a client
const client = new FhirClient({
baseUrl: "https://fhir.example.com",
auth: {
type: "bearer",
token: "your-access-token",
},
});
// Read a resource
const patient = await client.read("Patient", "patient-123");
console.log(patient.name);
// Search with query builder
import { SearchParamsBuilder } from "fhir-rest-client";
const params = new SearchParamsBuilder()
.where("family", "Smith")
.where("birthdate", "gt2000-01-01")
.sort("birthdate", "desc")
.count(10)
.build();
const bundle = await client.search("Patient", params);Authentication
Bearer Token
const client = new FhirClient({
baseUrl: "https://fhir.example.com",
auth: {
type: "bearer",
token: "your-access-token",
},
});Client Credentials
const client = new FhirClient({
baseUrl: "https://fhir.example.com",
auth: {
type: "client",
tokenUrl: "https://auth.example.com/token",
clientId: "your-client-id",
clientSecret: "your-client-secret",
},
});Password Flow
const client = new FhirClient({
baseUrl: "https://fhir.example.com",
auth: {
type: "password",
tokenUrl: "https://auth.example.com/token",
clientId: "your-client-id",
username: "[email protected]",
password: "your-password",
},
});PKCE (Authorization Code with Proof Key)
import { FhirClient, generatePkceChallenge } from "fhir-rest-client";
const { codeVerifier, codeChallenge } = await generatePkceChallenge();
const client = new FhirClient({
baseUrl: "https://fhir.example.com",
auth: {
type: "pkce",
tokenUrl: "https://auth.example.com/token",
authorizeUrl: "https://auth.example.com/authorize",
clientId: "your-client-id",
redirectUri: "https://yourapp.com/callback",
codeVerifier,
codeChallenge,
},
});
// After user authorization, exchange code for token
await client.auth.signIn({ code: "authorization-code" });CRUD Operations
// Create
const newPatient = await client.create("Patient", {
resourceType: "Patient",
name: [{ family: "Smith", given: ["John"] }],
});
// Read
const patient = await client.read("Patient", "patient-123");
// Update
patient.telecom = [{ system: "phone", value: "555-1234" }];
const updated = await client.update("Patient", "patient-123", patient);
// Delete
await client.delete("Patient", "patient-123");
// Patch (JSON Patch)
await client.patch("Patient", "patient-123", [
{ op: "replace", path: "/active", value: false },
]);Search
// Simple search
const results = await client.search("Patient", { family: "Smith" });
// Fluent query builder
import { SearchParamsBuilder } from "fhir-rest-client";
const params = new SearchParamsBuilder()
.where("family", "Smith")
.where("birthdate", "gt2000-01-01")
.where("active", "true")
.sort("birthdate", "desc")
.count(20)
.include("Patient", "organization")
.revInclude("Observation", "subject")
.build();
const bundle = await client.search("Patient", params);
// Pagination
if (bundle.link) {
const nextPage = await client.searchByUrl(
bundle.link.find((l) => l.relation === "next")?.url,
);
}Caching
const client = new FhirClient({
baseUrl: "https://fhir.example.com",
cache: {
enabled: true,
maxSize: 1000, // Max cached items
ttl: 300000, // 5 minutes TTL
invalidateOnMutate: true, // Auto-invalidate on create/update/delete
},
});
// Reads are cached automatically
const patient1 = await client.read("Patient", "123"); // Cache miss
const patient2 = await client.read("Patient", "123"); // Cache hit
// Updates invalidate cache
await client.update("Patient", "123", updatedPatient); // Cache invalidated
const patient3 = await client.read("Patient", "123"); // Cache missRetry & Error Handling
const client = new FhirClient({
baseUrl: "https://fhir.example.com",
retry: {
enabled: true,
maxRetries: 3,
baseDelay: 1000, // 1 second
maxDelay: 30000, // 30 seconds
factor: 1.5, // Exponential backoff
},
});
try {
const patient = await client.read("Patient", "invalid-id");
} catch (error) {
if (error instanceof ResourceNotFoundError) {
console.error("Patient not found");
} else if (error instanceof OperationOutcomeError) {
console.error("FHIR error:", error.outcome);
} else if (error instanceof NetworkError) {
console.error("Network error:", error.message);
}
}Auto-Batching
const client = new FhirClient({
baseUrl: "https://fhir.example.com",
batch: {
enabled: true,
maxBatchSize: 100,
windowMs: 50, // Batch requests within 50ms window
},
});
// These requests are automatically batched into a single Bundle
const [patient1, patient2, patient3] = await Promise.all([
client.read("Patient", "1"),
client.read("Patient", "2"),
client.read("Patient", "3"),
]);WebSocket Subscriptions
import { ClientSubscriptionManager } from "fhir-rest-client";
const subscriptionManager = new ClientSubscriptionManager({
wsUrl: "wss://fhir.example.com/ws",
token: "your-token",
reconnect: true,
reconnectInterval: 5000,
});
// Subscribe to resource updates
subscriptionManager.on("notification", (event) => {
console.log("Resource updated:", event.resource);
});
subscriptionManager.on("error", (error) => {
console.error("Subscription error:", error);
});
await subscriptionManager.connect();
// Subscribe to specific criteria
await subscriptionManager.subscribe({
resourceType: "Subscription",
id: "subscription-123",
criteria: "Observation?status=final",
});IG Loading (v0.2.0)
Load and browse ImplementationGuide data with built-in caching and ETag support.
import { MedXAIClient } from "fhir-rest-client";
const client = new MedXAIClient({
baseUrl: "http://localhost:8080",
igCacheEnabled: true, // Enable L2 IndexedDB cache (optional)
});
// List all imported IGs
const igs = await client.loadIGList();
// → IGSummary[] with id, url, version, name, status
// Load IG content index (profiles, extensions, valueSets, etc.)
const index = await client.loadIGIndex("us-core");
// → IGIndex with profiles[], extensions[], valueSets[], codeSystems[]
// Load a StructureDefinition with dependencies
const result = await client.loadIGStructure("us-core", "us-core-patient");
// → { sd: StructureDefinition, dependencies: ["http://..."] }
// Batch-load multiple resources (deduplicates cached ones)
const resources = await client.loadIGBundle("us-core", [
"StructureDefinition/us-core-patient",
"StructureDefinition/us-core-observation-lab",
]);Cache Layers
| Layer | Storage | Lifetime | Invalidation | | ----- | ---------- | ------------- | -------------------------- | | L1 | Memory LRU | Session | Page refresh | | L2 | IndexedDB | Cross-session | IG version change | | L3 | Server | Permanent | ETag / If-None-Match → 304 |
API Reference
FhirClient
Main client class for interacting with FHIR servers.
Methods:
read<T>(type, id, options?)— Read a resource by IDcreate<T>(type, resource, options?)— Create a new resourceupdate<T>(type, id, resource, options?)— Update a resourcepatch(type, id, patch, options?)— Patch a resource with JSON Patchdelete(type, id, options?)— Delete a resourcesearch<T>(type, params?, options?)— Search resourcessearchByUrl<T>(url, options?)— Search by full URLhistory(type?, id?, params?, options?)— Get resource historybatch(bundle, options?)— Execute a batch/transaction Bundlecapabilities(options?)— Get server CapabilityStatement
SearchParamsBuilder
Fluent builder for constructing FHIR search parameters.
Methods:
where(param, value)— Add search parametersort(param, order?)— Add sort parametercount(n)— Set page sizeoffset(n)— Set offset (for servers that support it)include(resourceType, param)— Add _includerevInclude(resourceType, param)— Add _revincludesummary(mode)— Set _summary modeelements(...fields)— Set _elementsbuild()— Build final SearchParams object
TypeScript Support
Full TypeScript definitions included:
import type {
Resource,
Bundle,
OperationOutcome,
FhirClientOptions,
SearchParams,
RequestOptions,
} from "fhir-rest-client";Browser Support
Works in all modern browsers with native fetch and crypto support:
- Chrome/Edge 90+
- Firefox 88+
- Safari 14+
Node.js Support
Requires Node.js 18+ (native fetch and crypto.subtle support).
License
Apache-2.0
Contributing
Contributions welcome! Please see CONTRIBUTING.md for details.
