npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

@nirmiteeio/fhir-sdk

v1.0.2

Published

Universal FHIR SDK for SMART on FHIR integrations. Supports Epic, Cerner, Allscripts, Athena Health, and more. Framework-agnostic with React hooks included.

Readme

@nirmitee/fhir-sdk

npm version License: MIT TypeScript

Universal FHIR SDK for SMART on FHIR integrations. Framework-agnostic with first-class React support.

Features

  • Multi-EMR Support - Pre-configured for Epic, Cerner, Athena Health, Allscripts, NextGen, Meditech, and eClinicalWorks
  • SMART on FHIR Compliant - Full OAuth 2.0 with PKCE support
  • Type-Safe - Complete TypeScript definitions for FHIR R4 resources
  • Framework Agnostic - Works with React, Vue, Angular, Svelte, or vanilla JavaScript
  • React Hooks - Optional hooks for convenient React integration
  • Auto Token Refresh - Handles token expiration automatically
  • Batch Operations - Efficient parallel data fetching with controlled concurrency
  • Pagination Support - Automatic pagination for large result sets
  • Modular - Use only what you need
  • Extensible - Easy to add custom EMR providers
  • Well Tested - 72% code coverage with 128 tests

Installation

npm install @nirmitee/fhir-sdk

For React projects:

npm install @nirmitee/fhir-sdk react@^18

Quick Start

React (Recommended)

The easiest way to get started with React:

import { useFHIR } from '@nirmitee/fhir-sdk/hooks';

function PatientDashboard() {
  const {
    // Authentication
    isAuthenticated,
    login,
    logout,

    // Patient data (auto-fetched)
    patient,
    medications,
    vitals,
    appointments,
    isDataLoading,
    refetch,
  } = useFHIR('epic'); // or 'cerner', 'athena', 'allscripts'

  if (!isAuthenticated) {
    return <button onClick={login}>Login with Epic</button>;
  }

  if (isDataLoading) return <div>Loading patient data...</div>;

  return (
    <div>
      <h1>Welcome {patient?.name?.[0]?.given?.[0]}!</h1>
      <button onClick={logout}>Logout</button>
      <button onClick={refetch}>Refresh Data</button>

      <h2>Medications ({medications.length})</h2>
      {medications.map(med => (
        <div key={med.id}>
          {med.medicationCodeableConcept?.text}
        </div>
      ))}

      <h2>Vitals ({vitals.length})</h2>
      {vitals.map(vital => (
        <div key={vital.id}>
          {vital.code.text}: {vital.valueQuantity?.value} {vital.valueQuantity?.unit}
        </div>
      ))}
    </div>
  );
}

Vanilla JavaScript / TypeScript

Framework-agnostic usage:

import { SMARTAuthClient, FHIRClient, PatientService } from '@nirmitee/fhir-sdk';

// 1. Initialize auth client
const authClient = new SMARTAuthClient('epic');

// 2. Initiate login
const authUrl = await authClient.authorize();
window.location.href = authUrl;

// 3. Handle callback (on redirect page)
await authClient.handleCallback(window.location.href);

// 4. Create FHIR client
const fhirClient = new FHIRClient({
  providerId: 'epic',
  authClient: authClient,
  autoRefreshToken: true,
});

// 5. Fetch patient data
const patientService = new PatientService(fhirClient);
const patientId = await authClient.getPatientId();
const medications = await patientService.getMedications(patientId);

Vue.js

<script setup>
import { ref, onMounted } from 'vue';
import { SMARTAuthClient, FHIRClient } from '@nirmitee/fhir-sdk';

const authClient = new SMARTAuthClient('epic');
const fhirClient = new FHIRClient({ providerId: 'epic', authClient });

const patient = ref(null);
const isAuthenticated = ref(false);

onMounted(async () => {
  isAuthenticated.value = await authClient.isAuthenticated();
  if (isAuthenticated.value) {
    const patientId = await authClient.getPatientId();
    patient.value = await fhirClient.read('Patient', patientId);
  }
});

const login = async () => {
  const authUrl = await authClient.authorize();
  window.location.href = authUrl;
};
</script>

<template>
  <button v-if="!isAuthenticated" @click="login">Login</button>
  <div v-else>Welcome {{ patient?.name?.[0]?.given?.[0] }}!</div>
</template>

Core Features

FHIR Client Operations

import { FHIRClient } from '@nirmitee/fhir-sdk';

const fhirClient = new FHIRClient({ providerId: 'epic', authClient });

// Read a specific resource
const patient = await fhirClient.read('Patient', 'patient-id');

// Search for resources
const medications = await fhirClient.search('MedicationRequest', {
  patient: 'patient-id',
  status: 'active',
});

// Search by patient
const vitals = await fhirClient.searchByPatient('Observation', 'patient-id', {
  category: 'vital-signs',
  _sort: '-date',
});

Batch Operations

Efficiently fetch multiple resources in parallel:

// Fetch multiple resources at once
const results = await fhirClient.batchFetch([
  { resourceType: 'Patient', params: { id: 'patient-123' }, key: 'patient' },
  { resourceType: 'Observation', params: { patient: 'patient-123', category: 'vital-signs' }, key: 'vitals' },
  { resourceType: 'MedicationRequest', params: { patient: 'patient-123' }, key: 'meds' }
], {
  parallel: true,
  maxConcurrent: 3,
  continueOnError: true
});

console.log(results.patient);  // Patient resource
console.log(results.vitals);   // Array of Observations
console.log(results.meds);     // Array of MedicationRequests

Fluent batch builder API:

const results = await fhirClient.batch()
  .read('Patient', 'patient-123', 'patient')
  .search('Observation', { patient: 'patient-123', category: 'vital-signs' }, 'vitals')
  .search('MedicationRequest', { patient: 'patient-123' }, 'meds')
  .execute({ maxConcurrent: 3 });

Pagination Support

Automatically handle large result sets:

// Fetch all pages automatically
const result = await fhirClient.getAllResources('Observation', {
  patient: 'patient-123',
  category: 'laboratory'
}, {
  maxPages: 10,
  maxResources: 500,
  onProgress: (page, total) => console.log(`Page ${page}, Total: ${total}`)
});

console.log(`Fetched ${result.totalFetched} resources`);
console.log(result.resources);

Memory-efficient iterator pattern:

// Process pages one at a time
for await (const page of fhirClient.iterateResources('Observation', {
  patient: 'patient-123'
}, 100)) {
  console.log(`Processing ${page.length} observations`);
  for (const observation of page) {
    await processObservation(observation);
  }
}

Request/Response Interceptors

Add custom logic to all requests:

// Add custom headers
fhirClient.addRequestInterceptor(async (url, options) => {
  return {
    url,
    options: {
      ...options,
      headers: {
        ...options.headers,
        'X-Custom-Header': 'value',
      },
    },
  };
});

// Transform responses
fhirClient.addResponseInterceptor(async (response, data) => {
  console.log('Response received:', data);
  return data;
});

Supported EMR Providers

| Provider | ID | PKCE | Refresh Token | Status | |----------|-----|------|---------------|--------| | Epic Systems | epic | Yes | Yes | Fully Tested | | Cerner (Oracle Health) | cerner | No | Yes | Fully Tested | | Athena Health | athena | Yes | Yes | Fully Tested | | Allscripts | allscripts | No | No | Fully Tested | | NextGen Healthcare | nextgen | Yes | - | Template | | Meditech | meditech | Yes | - | Template | | eClinicalWorks | eclinicalworks | Yes | - | Template |

Adding Custom EMR Provider

import { emrRegistry } from '@nirmitee/fhir-sdk';

emrRegistry.registerProvider({
  id: 'custom-emr',
  name: 'Custom EMR',
  authUrl: 'https://custom-emr.com/oauth/authorize',
  tokenUrl: 'https://custom-emr.com/oauth/token',
  fhirBaseUrl: 'https://custom-emr.com/fhir/r4',
  clientId: process.env.CUSTOM_EMR_CLIENT_ID,
  redirectUri: process.env.REDIRECT_URI,
  scopes: ['patient/*.read', 'openid'],
  oauth: {
    flow: 'authorization_code',
    pkce: true,
  },
  quirks: {
    acceptHeader: 'application/fhir+json',
    patientIdLocation: 'token.patient',
  },
});

Environment Variables

# Epic
NEXT_PUBLIC_EPIC_CLIENT_ID=your-client-id

# Cerner
NEXT_PUBLIC_CERNER_CLIENT_ID=your-client-id

# Athena
NEXT_PUBLIC_ATHENA_CLIENT_ID=your-client-id

# Allscripts
NEXT_PUBLIC_ALLSCRIPTS_CLIENT_ID=your-client-id

# Common
NEXT_PUBLIC_REDIRECT_URI=http://localhost:3000/callback

API Reference

Core Classes

SMARTAuthClient

OAuth 2.0 authentication with PKCE support.

const authClient = new SMARTAuthClient(providerId, options?);

// Methods
await authClient.authorize();              // Generate auth URL
await authClient.handleCallback(url);      // Handle OAuth callback
await authClient.getAccessToken();         // Get current token
await authClient.refreshAccessToken();     // Refresh token
await authClient.isAuthenticated();        // Check auth status
await authClient.getPatientId();           // Get patient ID
await authClient.logout();                 // Clear session

FHIRClient

FHIR resource operations with interceptor support.

const fhirClient = new FHIRClient({ providerId, authClient });

// Methods
await fhirClient.read<T>(resourceType, id);
await fhirClient.search<T>(resourceType, params);
await fhirClient.searchByPatient<T>(resourceType, patientId, params);
await fhirClient.batchFetch(requests, options);
await fhirClient.getAllResources<T>(resourceType, params, options);
fhirClient.iterateResources<T>(resourceType, params, delayMs);
fhirClient.addRequestInterceptor(interceptor);
fhirClient.addResponseInterceptor(interceptor);

PatientService

High-level patient data operations.

const service = new PatientService(fhirClient);

// Methods
await service.getPatient(patientId);
await service.getMedications(patientId, options?);
await service.getVitals(patientId, options?);
await service.getLabReports(patientId, options?);
await service.getAppointments(patientId, options?);
await service.getEncounters(patientId, options?);
await service.getProcedures(patientId, options?);
await service.getAllPatientData(patientId);

React Hooks

useFHIR(providerId)

All-in-one hook combining authentication and data fetching.

Returns:

{
  // Authentication
  isAuthenticated: boolean;
  isAuthLoading: boolean;
  patientId: string | null;
  login: () => Promise<void>;
  handleCallback: (url: string) => Promise<void>;
  logout: () => Promise<void>;

  // Clients
  fhirClient: FHIRClient | null;
  patientService: PatientService | null;

  // Patient data
  patient: Patient | null;
  medications: MedicationRequest[];
  vitals: Observation[];
  labReports: Observation[];
  appointments: Appointment[];
  encounters: Encounter[];
  procedures: Procedure[];

  // State
  isDataLoading: boolean;
  errors: Record<string, string>;
  refetch: () => Promise<void>;
}

Composable Hooks

import { useAuth, useFHIRClient, usePatientService } from '@nirmitee/fhir-sdk/hooks';

const auth = useAuth('epic');
const fhirClient = useFHIRClient('epic', auth.authClient);
const patientService = usePatientService(fhirClient);

Architecture

The SDK is built with modularity in mind:

Layer 5: React Hooks (Optional)
  └─ useFHIR, useAuth, usePatientData

Layer 4: Services (Optional)
  └─ PatientService (or create your own)

Layer 3: FHIR Client
  └─ FHIRClient

Layer 2: Auth Client
  └─ SMARTAuthClient

Layer 1: Foundation
  └─ EMR Registry, Storage, PKCE Utils

Use only what you need! The SDK is framework-agnostic at its core, with React hooks as an optional convenience layer.

Error Handling

import { FHIRError } from '@nirmitee/fhir-sdk';

try {
  const patient = await patientService.getPatient('patient-id');
} catch (error) {
  if (error instanceof FHIRError) {
    console.error('FHIR Error:', {
      message: error.message,
      statusCode: error.statusCode,
      resourceType: error.resourceType,
      providerId: error.providerId,
      operationOutcome: error.operationOutcome,
    });
  }
}

EMR-Specific Quirks Handled

The SDK automatically handles EMR-specific behaviors:

  • Epic: Requires specific status filters for MedicationRequest and Appointment
  • Cerner: Uses application/json instead of application/fhir+json
  • Athena: Returns 403 for restricted resources (treated as not found)
  • Athena: Patient ID extracted from JWT id_token claims
  • All: Different PKCE requirements and scope configurations

TypeScript Support

Full TypeScript support with FHIR R4 type definitions:

import type { Patient, Observation, MedicationRequest } from '@ahryman40k/ts-fhir-types/lib/R4';

const patient: Patient = await fhirClient.read<Patient>('Patient', 'patient-id');
const vitals: Observation[] = await fhirClient.search<Observation>('Observation', {
  patient: 'patient-id',
  category: 'vital-signs'
});

Benefits

  • 90% Less Boilerplate - No more manual OAuth flows, token management, or API calls
  • Type Safety - Full TypeScript support with FHIR R4 types
  • Maintainability - EMR-specific logic centralized in one place
  • Extensibility - Add new EMRs via JSON configuration
  • Testing - Easy to mock and test
  • Framework Agnostic - Works with any JavaScript framework
  • Performance - Efficient batch operations and pagination

Documentation

Requirements

  • Node.js >= 18.0.0
  • For React projects: React >= 18.0.0

Testing

The SDK has comprehensive test coverage:

  • 128 tests
  • 72% code coverage
  • Unit tests for all core functionality
  • Integration tests for OAuth flows

Contributing

Contributions are welcome! Please visit our GitHub repository to:

  • Report issues
  • Submit pull requests
  • Request features
  • View contribution guidelines

License

MIT © 2025 Nirmitee

See LICENSE file for details.

Links

Support

For questions and support:


Made with ❤️ by Nirmitee