Skip to content

FHIR Client

BabelFHIR-TS generates a type-safe FHIR client that extends the version-matched base client (@babelfhir-ts/client-r4, client-r4b, or client-r5) with profile-specific methods.

Basic Usage

ts
import { FhirClient } from "./output/fhir-client";

const client = new FhirClient("https://hapi.fhir.org/baseR4");

Profile-Specific Methods

The generated client adds methods named after your IG's profiles:

ts
// Read a US Core Patient by ID
const patient = await client.read().usCorePatient().read("123");

// Search for US Core Conditions
const bundle = await client.read().usCoreCondition().search({ patient: "123" });

Base FHIR Resources

All base resource types for the selected FHIR version are available:

ts
const appointment = await client.read().appointment().read("456");
const encounters = await client.read().encounter().search({ patient: "123" });

Skipping Client Generation

If you don't need the FHIR client (e.g., you already use another HTTP client), pass --no-client to skip generating the fhir-client/ module:

bash
babelfhir-ts --package hl7.fhir.us.core@8.0.0 --no-client

Base Client Package

The version-matched base client package (e.g., @babelfhir-ts/client-r4) is automatically included as a dependency of every generated package. It provides:

  • CRUD operations for all R4 resource types
  • Search with type-safe parameters
  • Bundle handling
  • Pagination support

The generated client extends this base with profile-specific accessor methods unique to your Implementation Guide.

Using Multiple Packages

When your project depends on multiple generated IG packages (e.g., Da Vinci PAS and Da Vinci DTR), each package would generate its own FhirClient subclass — but you only need one client instance. The recommended pattern:

  1. Install the base client directly:

    bash
    npm install @babelfhir-ts/client-r4
  2. Install each IG package with --no-client to skip generating per-package clients:

    bash
    babelfhir-ts install hl7.fhir.us.davinci-pas@2.1.0 --no-client
    babelfhir-ts install hl7.fhir.us.davinci-dtr@2.1.0 --no-client
  3. Use forType<T>() for profile-specific access from any package:

    ts
    import { FhirClient } from "@babelfhir-ts/client-r4";
    import type { PASClaim, PASCoverage } from "hl7.fhir.us.davinci-pas-generated";
    import type { DTRQuestionnaireResponse } from "hl7.fhir.us.davinci-dtr-generated";
    
    const client = new FhirClient("https://fhir.example.com", authFetch);
    
    // Base R4 types — named accessors (inherited from @babelfhir-ts/client-r4)
    const patient = await client.read().patient().read("123");
    
    // Profile types from any package — forType<T>()
    const claim = await client.read().forType<PASClaim>("Claim").search({ status: "active" });
    const coverage = await client.read().forType<PASCoverage>("Coverage").searchAll({
      patient: "Patient/123",
    });
    const qr = await client.read().forType<DTRQuestionnaireResponse>("QuestionnaireResponse").read("456");
    
    // Write operations work the same way
    await client.write().forType<PASClaim>("Claim").create(myClaim);

forType<T>(resourceType) is available on both read() and write() clients and returns a fully typed reader/writer for any FHIR resource type T. The resourceType string must be the base FHIR resource type name (e.g., "Claim", not the profile name).

SMART on FHIR Authentication

The base client package includes built-in SMART App Launch (v2) support with PKCE. This is provided via @babelfhir-ts/smart-auth, which is re-exported from the client package.

SmartFhirClient

The easiest way to use SMART auth is through SmartFhirClient, which combines discovery, token management, and the typed FHIR client:

ts
import { SmartFhirClient } from "@babelfhir-ts/client-r4";

const smart = new SmartFhirClient({
  clientId: "my-app",
  redirectUri: "http://localhost:3000/callback",
  fhirBaseUrl: "https://fhir.example.com/fhir",
  scopes: "openid fhirUser patient/*.read",
});

// On page load: check if this is an OAuth callback
if (smart.isCallback()) {
  const token = await smart.handleCallback();
  // token.patient, token.fhirUser available from context
} else if (!smart.isAuthenticated()) {
  await smart.authorize(); // redirects to IdP
  return;
}

// Authenticated requests — all headers injected automatically
const patient = await smart.read().patient().read("123");

SmartAuth (Standalone)

For more control, use SmartAuth directly to manage the OAuth flow without coupling to the FHIR client:

ts
import { SmartAuth } from "@babelfhir-ts/client-r4";

const auth = new SmartAuth({
  clientId: "my-app",
  redirectUri: "http://localhost:3000/callback",
  fhirBaseUrl: "https://fhir.example.com/fhir",
  scopes: "openid fhirUser patient/*.read",
});

// Handles both standalone and EHR launch automatically
await auth.authorize();

// After callback:
const token = await auth.handleCallback();
const authFetch = auth.createAuthenticatedFetch();

Configuration

OptionTypeDescription
clientIdstringOAuth2 client_id registered with the auth server
redirectUristringRedirect URI registered with the auth server
fhirBaseUrlstringFHIR server base URL (used as aud parameter)
scopesstringSpace-separated OAuth2 scopes
storageStorageOptional storage backend (defaults to sessionStorage)
storagePrefixstringOptional prefix for storage keys (defaults to "smart_")
postLogoutRedirectUristringWhere to redirect after IdP logout

Launch Modes

SMART supports two launch modes, auto-detected from URL parameters:

  • Standalone — your app initiates the flow by redirecting to the authorization endpoint
  • EHR — the EHR redirects to your app with ?launch=...&iss=... parameters

SmartFhirClient.authorize() and SmartAuth.authorize() detect the mode automatically.

Token Context

After a successful callback, the SmartToken object includes:

ts
interface SmartToken {
  access_token: string;
  token_type: string;
  expires_in: number;
  scope: string;
  id_token?: string;
  refresh_token?: string;
  fhirUser?: string;   // e.g., "Practitioner/123"
  patient?: string;     // patient context from EHR launch
  encounter?: string;   // encounter context from EHR launch
}

Endpoint Discovery

The library discovers OAuth endpoints automatically via:

  1. /.well-known/smart-configuration (preferred)
  2. /metadata CapabilityStatement fallback

You can also call discoverEndpoints() directly:

ts
import { discoverEndpoints } from "@babelfhir-ts/client-r4";

const config = await discoverEndpoints("https://fhir.example.com/fhir");
// config.authorization_endpoint, config.token_endpoint, etc.

Released under the ISC License.