This unit is about developing FHIR clients using reference libraries, and connecting to Argonaut/IPS-compliant FHIR servers.

We will present the general concepts for all client libraries (how to perform common tasks in FHIR: connecting, accessing resources, etc.) and then describe how each library allows you to do each task.

So, this unit in fact contains three different subunits on how to use each library (Java, .NET, JavaScript).

These subunits have a set of micro assignments. These assignments will not be graded and the solutions will be posted along with the reading material. You can choose to try to solve them by yourself or just review and try the solutions.

What you'll learn

After Unit 2, you will be able to:

This specific subunit talks about the fhir-kit-client library, so we call this "Our Fhir-Kit-Client Handbook."

Note:

It's not "THE Fhir-Kit-Client Handbook" because we included only the information relevant for you to be able to fulfill this unit's goals.

The options for using FHIR + JavaScript present a lot of variations (which library, which framework, etc.), and creating a full handbook would take thousands of pages, and that is outside of this course's scope (or any course's scope, maybe).

In the following sections of this chapter we will explain how one of the available FHIR libraries allows you to implement all the functionalities described in the first unit of this module (FHIR clients).

Sometimes the library does not implement one of FHIR's functionalities; in those cases we give you some ideas on how to implement them using other libraries.

Throughout these sections, you will see three different icons:

Sometimes to illustrate a method we use both examples and recipes, and sometimes only examples, depending on the subject's complexity.

In contrast to the wide use and dominance of .NET FHIR API and FHIR HAPI in the .NET and Java world, in JavaScript there are at least three mature options (we wrote this in December 2019, so it may have changed) for implementing FHIR.

FHIR.JS: https://github.com/FHIR/fhir.js

Open Source (MIT License), supports CRUD operations, Adapters for Angular, JQuery, support for headers and cookies, oAuth2 & Basic authentication. This is the oldest library, almost a reference library, sometimes used by the others, not very often updated.

client-js: http://docs.smarthealthit.org/client-js/

Open Source (Apache 2.0 license), mostly built to support and facilitate launching and read-only operations on resources (search, get) to Argonaut-enabled servers, but with added functionality in release 2 to support other CRUD operations.

Maintained by the SMART on FHIR group (we will see more about this in subsequent units).

If you are interested in bulk operations on FHIR, SMART also provides libraries for bulk operations; we recommend you take a look at https://smarthealthit.org/developer-resources/ under "FHIR bulk data."

They also publish R, Ruby, Swift and Python libraries, so if you are interested in using those languages with FHIR, also take a look at the developer resources at smarthealthit.org.

fhir-kit-client: https://github.com/Vermonster/fhir-kit-client

Open Source (MIT license), covers all the basic (CRUD) and advanced operations, pagination, batch, transactions, support for contained, in-bundle and absolute references.

Can be used only with node.js clients.

Developed by Vermonster (https://www.vermonster.com/health), a company based in Boston.

FHIR.js: https://www.npmjs.com/package/fhir - same name as the first one. Sorry about that – people are not that creative when naming libraries.

Open source, developed by Lantana. Node.JS library for serializing/deserializing FHIR resources between JS and XML, and validating FHIR resources.

We selected fhir-kit-client because it has good coverage of our needs.

However, we will also work with client-js during the SMART on FHIR units, so you will use both.

Downloading the client.js library

npm i fhir-kit-client

If you want to manually download the library, use the GitHub link: https://github.com/Vermonster/fhir-kit-client

Documentation for client-js is at both the GitHub link and the NPM link here: https://www.npmjs.com/package/fhir-kit-client

Referencing the client.js library

const Client  = require('fhir-kit-client')

General comments

Remember that JavaScript is not strongly typed as C# and Java are, so we will discuss data types, but do not rely on the library for this.

However, there is a TypeScript library to achieve strong typing for FHIR objects:

https://github.com/Ahryman40k/typescript-fhir-types

It's still a little immature, but feel free to play with it.

FHIR version / Context

Fhir-kit-client supports all variants of R4, STU3 and DSTU2 1.0.2

Client / Endpoint

To create a FHIR client, instantiate a Client() object with the endpoint parameter.

Example #S-1: Create FHIR Client

    const fhirClient = new Client({
    baseUrl: 'https://hl7-ips-server.hl7.org/fhir'
    });

The endpoint is the address of the server without specifying any resource.

This client variable will carry all operations against the server.

Micro Assignment #S-1: Set up your fhir-kit-client environment

A) After installing node, from a terminal, type the following commands:

mkdir ma_s01  
cd ma_s01  
npm init  
npm i express --save  
npm i fhir-kit-client --save  

What you are doing here:

These last two commands will install express and the fhir-kit-client and all their dependencies.

B) Create a file called ma_s01.js in the same folder, with this content:

const Client  = require('fhir-kit-client')  
const fhirClient = new Client({  
    baseUrl: 'https://hl7-ips-server.hl7.org/fhir'  
    });  
console.log ('I am just a skeleton, so I do nothing!') 

C) Now you are ready to run your app, executing from the terminal or command line:

node ma_s01.js

The expected result should be our message "I am just a skeleton, so I do nothing!" And no errors.

Fhir-kit-client comes with a built-in authorization bearer parameter, and you can use any other authorization strategy using the customHeader parameter.

Authentication with Bearer Token

Create the request header using the bearerToken parameter.

Bearer Token Authentication with fhir-kit-client

const fhirClient = new Client({
    baseUrl: 'https://hl7-ips-server.hl7.org/fhir',
    bearerToken: 'ya29.QQIBibTwvKkE39hY8mdkT_mXZoRh7Ub9cK9hNsqrxem4QJ6sQa36VHfyuBe'
    });  

Basic Authentication

Remember that a basic authentication server requires sending your user name and password. Basic authentication is not recommended, but if you need to implement it, this is how it's done.

Basic Authentication with fhir-kit-client

authinfo="user:password";
let buff = new Buffer.from(authinfo);
let base64data = buff.toString('base64');
const fhirClient = new Client({
    baseUrl: 'https://hl7-ips-server.hl7.org/fhir',
    customHeaders:["Authorization","Basic " + base64data]});

Micro Assignment #S-2: Try different authentication methods

  1. Modify ma_s01.js to use Bearer Token authentication.
  2. Modify ma_s01.js to use Basic Authentication.

Headers for Encoding

Note: Due to it being a native JS library, the use of XML encoding of resources is unlikely. However, since this is one of the options in FHIR, we also include it in this context.

In fhir-kit-client, this is one of the custom headers, allowing you to choose which syntax you expect to send and/or receive from the server:

Recipe #S-1: Setting the Resource Encoding (JSON or XML)

const fhirClient = new Client({
    baseUrl: 'https://hl7-ips-server.hl7.org/fhir',
    customHeaders:{
    "Content-Type":"|RequestEncoding|",
    "Accept":"|ResponseEncoding|"
    }

You need to replace:

|RequestEncoding|

application/fhir+json application/fhir+xml

This will be the encoding for the body, so if you are using the body at all, it is NOT optional

|ResponseEncoding|

application/fhir+json application/fhir+xml

Other headers:

fhir-kit-client allows the inclusion of any other header using the same technique (adding headers to the request before issuing it).

Micro Assignment #S-3: Change Fhir-kit-client encoding

Modify the ma_s01.js to force the use of JSON encoding.

Right now we will cover the traditional RESTful methods (CRUD): Create, Read, Update and Delete Resource, and in the last steps of this subunit we will cover Searching.

Always remember to:

  1. Require the client in order to use the fhir-kit methods.
  2. Catch the rejected promises (you will get an unhandled error otherwise).

Recipe #S-2: Importing Model Classes, catching the rejected promises

const fhirClient  = require('fhir-kit-client')
fhirClient.|operation|({  
    |OperationParameters|
 }).then((MyResult) =>{...})   
  .catch((err)=>{...})   

Direct Read (READ)

In order to perform a direct read of a FHIR resource instance when you know the resource id, using fhir-kit-client, you need to do this:

Recipe #S-3: Direct Read

fhirClient.read({  
    resourceType: '|ResourceClass|',  
   id: |ResourceId|,  
 }).then((MyPatient) =>{...})   
  .catch((err)=>{...})   
// Do whatever you want with MyPatient: it's your resource as an object  
// Remember to Catch the error!  

You need to replace

|ResourceClass|

the name of the resource class. Example: Patient

|ResourceId|

the server-assigned logical id of the resource

Example #S-2: Read a patient instance having a logical id – in this case 17343

fhirClient.read({
    resourceType: 'Patient',
    id: 17343,
  }).then((MyPatient) =>  

Version Read (VREAD)

In order to perform a version-aware read of a specific version of a FHIR resource instance, knowing the resource id and version, using fhir-kit-client, you need to do this:

Recipe #S-4:Version Read

fhirClient.vread({  
 resourceType: '|ResourceClass|',  
        id: |ResourceId|,  
        version:|Version|  
      }).then((MyPatient) =>      

You need to replace

|ResourceClass|

the name of the resource class. Example: Patient

|ResourceId|

the server-assigned logical id of the resource

|Version|

the version of the resource

Example #S-3: Read a Specific Version of a Resource Instance

fhirClient.vread({  
 resourceType: 'Patient',  
        id: 17343,  
        version:1  
      }).then((MyPatient) =>      

Direct Read of a Resource by URL

fhir-kit-client allows direct reading of referenced resources, using the resolve method. You need to specify the URLfor the resource.

Recipe # S-5: Direct Read with URL

fhirClient.resolve(  
    { reference: '|ResourceUrl|' })  
    .then((MyPatient) =>  

You need to replace

|ResourceUrl|

the full address endpoint + assigned id/version of the resource

Example #S-4: Read a Resource Using URL

fhirClient.resolve(  
    { reference: 'https://hl7-ips-server.hl7.org/fhir/Patient/3' })  
    .then((MyPatient) =>  

This would also read the patient with id=17343 in our server.

Micro Assignment #S-4: Read using all methods

  1. Modify ma_s01.js to read patient with id 17343
  2. Modify ma_s01.js to read patient with id 17343 / Version 1
  3. Modify ma_s01.js to read patient with id 17343 using the full resource URL

Creating a new instance of a resource class

In order to create a new instance of a FHIR resource using fhir-kit-client, you need to follow these steps:

Recipe # S-6: Create a Resource Instance

  1. Create and populate the resource instance: You need to represent the instance as you would write it in Notepad. There is no need for specific methods. It's just JSON in JavaScript.
  2. Invoke the create method in the server.
  3. Process the server response (outcome).
//Step 1: Create the Instance  
const newResource= {  
       "resourceType": "|ResourceType|",  
        "|SimpleElementName|": "|SimpleElementValue|",  
       "|ComplexElementName|": { "|SimpleElementName_1|":"SimpleElementValue_1|"  
                                                            , or {|ComplexElementName|:...}  
                                to N        }  
        "|RepeatableElementName|":[ { "|SimpleElementName1|":"SimpleElementValue1|"  
                                                            , or {|ComplexElementName1|:...}}, to N  
                                          
                                          ], to M}  
 //Step 2. Invoke in the Server and 3. process the response (then/catch) 
fhirClient.create({
resourceType: '|ResourceClass|',
body: newResource,})
 .then((data) => {var NewId=data.id;
                  res.send("Id:"+NewId);})
.catch((error) => { 
                  var errorText=JSON.stringify(error);
                  res.send(errorText) 
});

|ResourceClass|

The name of the resource class. Example: Patient

|ElementName|,

|ElementValue|

The name and value - data type valid - of the element to fill in the resource

|ElementType|

FHIR Type of the element to create

newResource

The variable holding the instance you are creating

Data

The variable holding the instance created by the server as a response

Example #S-5: Create a Practitioner Instance

Note: This example is long, so we are not rendering it here, but you can follow the GitHub link.

Practitioner Data:

Dellacroix, Madeleine, Canada Practitioner # (http://canada.gov/cpn: 51922)

Phone #: 613-555-0192 Address: 3766 Papineau Avenue, Montreal, Quebec, H2K 4J5

Email: qcpamxms9dq@groupbuff.com Specialty: Gynecologist (http://canada.gov/cpnq : OB/GYN)

Micro Assignment #S-5: Create a new Patient resource

Patient data:

Smith, Alan, born 06 May 1965, male, mrn: http://testpatient.id/mrn/ 99999999

Recipe #S-7: Update a Resource Instance

In order to update an existing instance of a FHIR resource, you need to follow these steps:

  1. Load the existing resource instance from the server using any of the techniques described in our previous "Read" section.
  2. Modify or add the needed resource attributes.
  3. Invoke the update method in the server.
  4. Process the server response (outcome).
fhirClient.read({  
        resourceType: '|ResourceClass|',  
        id: |ResourceId|,  
    }).then((MyResource) => UpdateResource(MyResource))  
    .catch((error) => console.log(JSON.stringify(error)));  
  
const UpdateResource(MyResource) {  
    MyResource.|modifiedElement| = |modifiedValue|;  
    fhirClient.update({  
            resourceType: '|ResourceClass|',  
            id: |ResourceId|,  
            body: MyResource,  
        }).then((data) => {  
            console.log("Success");  
            var NewVer = data.meta.versionId;  
            console.log("Updated VersionId:" + NewVer);  
        })  
        .catch((error) => {  
            console.log("Error");  
            var errorText = JSON.stringify(error);  
            console.log(errorText);  
        });  
  
}  

|ResourceClass|

The name of the resource class. Example: Patient

|modifiedElement| , |modifiedValue|

Simple or complex modified (or added) element names and values.

|ResourceId|

The resource id for the resource instance you need to update

Example #S-6: Update a Practitioner Resource Instance

We will update the resource for the new Practitioner resource adding the photo.

Micro Assignment #S-6: Update the Patient resource created in #S-5

This is the new patient information:

Patient Address: 3600 Papineau Avenue, Montreal, Quebec (H2K 4J5), Canada

Patient Phone #: 613-555-5555

How to delete an existing instance of a resource

Although it is rarely used, some libraries allow deletion of resources. Server-specific processing will determine if the deletion is processed or rejected.

In order to delete an existing instance of a FHIR resource, you need to follow these steps:

Recipe # S-8: Delete a Resource Instance

String ResourceLogicalId = "|ResourceClass|/|ResourceId|";  
client.Delete(ResourceLogicalId);  

|ResourceClass|

The name of the resource class. Example: Patient

|ResourceId|

The resource id for the resource instance you need to delete

Example #S-7: Delete Practitioner Resource Instance

fhirClient.delete({  
   resourceType: 'Practitioner',  
   id:74328,  
 }).then((data) => {   
       console.log("Success");})  
   .catch((error) => {   
       console.log("Error");  
       var errorText=JSON.stringify(error);  
       console.log(errorText);  
   });  

Micro Assignment #S-7: Delete the Patient resource created previously

We will explore some of the advanced methods in FHIR using Fhir-kit-client.

Advanced methods include

Obtaining the server's capability statement

Conformance in the Fhir-kit-client is represented using the capabilities() method of the client class.

Note: remember to import the appropriate classes from Fhir-kit-client.

To obtain the statement, just invoke it with the parameter ofType(CapabilityStatement.class).

Recipe #S-9: Obtain the Server Capability Statement

fhirClient.capabilityStatement(  
  ).then((data) => {   
        console.log(JSON.stringify(data));})  
    .catch((error) => {   
        console.log("Error");  
        var errorText=JSON.stringify(error);  
        console.log(errorText);  
    });  

The response is a resource, so it can be serialized to JSON or XML, or explored like any other FHIR resource.

Micro Assignment #S-8: Obtain the conformance resource of our course server

Endpoint: https://hl7-ips-server.hl7.org/fhir

fhir-kit-client CapabilityTool

fhir-kit-client does a very good job of letting us know which specific resources and CRUD/extended operations are available without having to go through the returned capability statement "manually", via a class called CapabilityTool.

To include the capability tool, use this code to require the library and get the contents of the capability statement.

const CapabilityTool = require('fhir-kit-client/lib/capability-tool');
const capabilities = new CapabilityTool(capabilityStatement);

After this, some of these handy methods are invoked using capabilities.|Method|(|parameters|)

Method

Description

capabilityContents (|resourceType|, |capabilityType|)

Check if a specific capability is supported for a resource type

interactionsFor(|resourceType|)

List of interactions supported for a resource

resourceCan(|resource|,|interaction|)

Check if a specific interaction is supported for a resource type

searchParamsFor(|resourceType|)

Search parameters for a resource type

serverCan(|interaction|)

General capability of a server (batch, interaction, document)

Answering common questions about a FHIR server

The capability tool assists in answering these common questions about a FHIR server. For example:

Micro Assignment #S-8b: Answer the questions for our server

Endpoint: https://hl7-ips-server.hl7.org/fhir

There are three different kinds of operations: applied to the whole server, to a specific resource, and to a specific instance of a specific resource.

None of the extended operations are supported by fhir-kit-client, so we resort to a standard node.js/javascript library: axios (https://www.npmjs.com/package/axios)

To install axios, execute:

 npm install axios

Instance Operations

Recipe # S-10: Executing an extended operation on a specific resource instance

  const axios   = require('axios');
  var urlFHIREndpoint='|EndPoint|';
  var ResourceClass  ='|ResourceClass|';
  var ResourceId = '|ResourceId|';
  var Operation  = '|OperationName|';
  var FullURL = urlFHIREndpoint+ResourceClass+"/"+ResourceId+"/"+Operation
  var params=
    {
    |Parameter_Name_1|: '|Parameter_Value_1|'
    |Parameter_Name_2|: '|Parameter_Value_2|',
    }
  axios.get(FullURL,
    {params}
    )
  .then(response => {
    console.log(response.data);
  })
  .catch(error => {
    console.log(error);
  });

You need to replace:

|ResourceClass|

The name of the resource class. Example: Patient

|ResourceId|

The resource id for the resource instance you need to delete

|Parameter_Name_n|

Name of the parameter

|Parameter_Value_n|

Value of the parameter

|OperationName|

Name of the operation (example: $everything)

Example #S-8: Executing the $everything extended operation

Executing the $everything method for a Patient instance for a given period.

  const axios   = require('axios');
  var urlFHIREndpoint='https://hl7-ips-server.hl7.org/fhir/';
  var ResourceClass  ='Patient';
  var ResourceId = '172191';
  var Operation  = '$everything';
  var FullURL = urlFHIREndpoint+ResourceClass+"/"+ResourceId+"/"+Operation
  var params=
    {
    start: '2015-10-01',
    end: '2016-02-10'
    }
  axios.get(FullURL,
    {params}
    )
  .then(response => {
    console.log(response.data);
  })
  .catch(error => {
    console.log(error);
  });

Micro Assignment #S-9: Use $everything to obtain all resources for a patient

Obtain all resources associated with patient 72191 from December 2019 to March 2020.

Conditional create is not currently supported by fhir-kit-client, so we again resorted to axios in order to execute the REST operation.

Remember to process the error condition and outcome (you should always process both anyway!) because three different conditions may occur:

Example #S-9: Conditional Create of a Practitioner instance - Controlled Outcome

  const axios   = require('axios');
  var urlFHIREndpoint='https://hl7-ips-server.hl7.org/fhir/';
  var ResourceClass  ='Practitioner';
  var SearchParameters  = 'identifier=http://central.practitioner.id/ident|77777';
  var FullURL = urlFHIREndpoint+ResourceClass+"?"+SearchParameters;
  const newPractitioner = {...your resource contents...}
  //We call the FHIR endpoint with our parameters
  axios.put(FullURL,newPatient)
  .then(response => {
    //We check the response status
    var status=response.status;
    //201: Created    ,  200: OK (Found)
    if (status==201)
    {console.log("Created "+response.data.id)}
    else 
    {console.log("Existing "+response.data.id)}
  })
  //Any other problem, we failed, like in 412.Precondition Failed
  .catch(error => {
    console.log(error.response.statusText);
  });

Micro Assignment #S-10: Use conditional create for a patient resource instance

Create a patient resource if it doesn't exist, searching by identifier: value (you need to create a new one) system=http://central.patient.id/ident.

Since terminology operations are not supported by fhir-kit-client, we again resorted to axios.

We will go through the most used terminology operations. There are a few more, but we are not covering those in the FHIR intermediate course.

We'll cover:

CodeSystem/$lookup

Recover all information from a specific code from a codesystem

ValueSet/$expand

Get all the concepts from a specific valueset, (optionally, matching some filter)

ValueSet/$validate_code

Validate a code against a ValueSet

In this template we are only obtaining the whole result from the server. Depending on the specific terminology, by going over all the parameters, you can extract information about hierarchies, properties, etc.

Recipe #S-11: Invoking CodeSystem / $lookup

const axios   = require('axios');
  var urlFHIREndpoint='|endpoint|';
  var ResourceClass  ='CodeSystem';
  var OperationName="$lookup"
  var code= "|code|"
  var system= "|code_system|";
  var Parameters="code="+code+"&"+"system="+system;
  var FullURL = urlFHIREndpoint+ResourceClass+"/"+OperationName+"?"+Parameters;
  //We call the FHIR endpoint with our parameters
  axios.get(FullURL)
  .then(response => {
    //We check the response status
    var data=response.data;
    {console.log(JSON.stringify(data));}
  })
  //Any other problem, we failed
  .catch(error => {
    console.log(error);
  });

|endpoint|

FHIR server base endpoint

|code|

code to lookup

|code_system|

code_system for the code

Micro Assignment #S-11: Validate using the lookup method

Use the lookup method to validate the code 73211009 for SNOMED CT.

Use the public FHIR endpoint for the SNOMED snowstorm server: https://snowstorm-alpha.ihtsdotools.org/fhir/

This may be the most useful and powerful terminology operation for mainstream clients: You can use this operation to populate a combo/list box or enable smart completion for a text box, and also for clinical decision support.

To parse the response, you need to traverse all the ValueSet.=> Expansion => Contains elements of the response.

Recipe #S-12: Invoking ValueSet / $expand

const axios   = require('axios');
  var urlFHIREndpoint='https://snowstorm-alpha.ihtsdotools.org/fhir/';
  var ResourceClass  ='ValueSet';
  var OperationName="$expand"
  var url= "http://snomed.info/sct"
  var filter= "";
  var Parameters="url="+url
  if (filter!=""){ Parameters=Parameters+"&"+"filter="+filter;}
  var FullURL = urlFHIREndpoint+ResourceClass+"/"+OperationName+"?"+Parameters;
  
  //We call the FHIR endpoint with our parameters
  
  axios.get(FullURL)
  .then(response => {
    //We check the response status
    var data=response.data;
    {console.log(JSON.stringify(data));}
  })
  //Any other problem, we failed
  .catch(error => {
    console.log(error);
  });

|url|

url to define which reference set or "domain" you are searching in the terminology server.

The general syntax for URL in SNOMED CT is

http://snomed.info/sct?fhir_vs=ecl/^|parameter|

or

http://snomed.info/sct?fhir_vs=isa/|parameter|

ecl: Expression Constraint Language, so you can replace |parameter| with any ECL expression

isa: is-a relationship, so the query will return all concepts with an is-a relationship with the |parameter|

|filter|

Further filtering of the concepts

Micro Assignment #S-12: ValueSet Expansion

  1. Search for all the concepts related to diabetes – 73211009 – (relationship: is-a).
  2. Search for all the concepts in the General Practice Reference Set (450970008). Extra filter: pain.

This operation requires identifying the ValueSet through its server id or URL, and the code and system to validate (not included, but remember your try/catch clauses).

Recipe # S-13: Invoking ValueSet / $validate_code

 const axios   = require('axios');
  var urlFHIREndpoint='https://fhir.loinc.org';
  var ResourceClass  ='ValueSet';
  var OperationName="$validate-code"
  var ValueSet= "|valueset_id|"
  var Code= "|code|";
  var CodeSystem="|code_system|";
  var Parameters="system="+CodeSystem;
  Parameters=Parameters+ "&"+"code="+Code;
           
   var fullURL = urlFHIREndpoint+"/"+ResourceClass+"/"+ValueSet+"/"+OperationName+"?"+Parameters;
   axios.get(fullURL)
  .then(response => {
    //We check the response status
    var data=response.data;
    {console.log(JSON.stringify(data));}
  })
  //Any other problem, we failed
  .catch(error => {
    console.log(error);
  });

|valueset_id|

Id for the valueset

|code|

Code needing validation

|code_system|

Code system for the code

Micro Assignment #S-13 : ValueSet Code Validation

Validate the code 8867-4 for the Value Set LG33055-1 (Group for Vitals -> Heart Rate) at https://fhir.loinc.org (note: this LOINC server by Regenstrief requires BasicAuth, and you need to open a free account).

The response should be: "Code Found."

Using Fhir-kit-client, there is no built-in validation method, so we need to call any FHIR server's validation method.

We achieve this by doing a POST on the FHIR server $validation method, with the resource we want to validate in the body. Remember to set the Content-Type in the request headers if you are using XML.

Recipe #S-14 : Basic Resource Validation

const axios   = require('axios');
var urlFHIREndpoint='|Endpoint|';
var ResourceClass="|ResourceClass|";
var OperationName="$validate";
var FullURL = urlFHIREndpoint+"/"+ResourceClass+"/"+OperationName;
axios.post(
    FullURL, content,
    {headers :  { "Content-Type":"|content_type|",
    "Accept":"application/fhir+json"}
    }
   )
.then(response => {
  //We check the response status. 
  //If it's OK the resource is valid
  var data=response.data;
  {console.log(JSON.stringify(data));}
})
//Any other problem, we failed
.catch(error => {
  var message=error;
  console.log(message.response.data.text);
});

|ResourceClass|

Resource Class of the Content you need to validate

|Endpoint|

Base Endpoint for the FHIR server

|content_type|

application/fhir+xml or application/fhir+json

content

String containing your resource's contents

Micro Assignment #S-14: Validate a Patient Resource

Create a program to read and validate the FHIR_RESOURCES/PATIENT.XML file against the FHIR R4 schema, using our course FHIR server.

It has only one error. Check that your program reports this error.

As you may already know, node.js/JavaScript are not strongly typed.

We will explore how to populate FHIR objects anyway, even when the notion of data types is a little funny for JavaScript and node.js.

Since JSON is just a syntax for JavaScript objects (JavaScript Object Notation), we can access (read and write) directly to the objects.

We will then explore how to populate resources and extract elements from resources, in the JavaScript way.

Maybe the only difference (and this is the same in all the other languages we use to manipulate FHIR resources) is

  1. between simple elements (regardless of the data type) and complex elements
  2. between elements that can repeat and elements that cannot repeat.

Write Methods: Populating Resources

For any element you need to populate in a resource, you will do:

Resource.|ElementName|=|ElementValue|

|ElementValue| can be

  1. A simple value, like in:
obs.status="final";
  1. A complex value, like in:
 obs.valueQuantity = {
    value: element.NUM_RESULT,
    unit: element.UNIT_RESULT,
    system: "http://unitsofmeasure.org/",
    code: element.CODE_RESULT
};                 
  1. A complex, repeating element, like in:
obs.code = {
    coding: [{
            system: "http://laborat.com/codesystems/observation_code",
            code: element.LOCAL_CODE,
            display: element.LOCAL_DESCRIPTION
        },
        {
            system: "http://loinc.org",
            code: element.LOINC_CODE,
            display: element.LOINC_DESCRIPTION
        }
    ]
};

If you have a lot of repeating elements to add to a resource (or element), you can opt for this strategy, using push:

obs.code.coding = [];
element = {
    system: "http://laborat.com/codesystems/observation_code",
    code: element.LOCAL_CODE,
    display: element.LOCAL_DESCRIPTION
};
obs.code.coding.push(element);

Read Methods: Extracting Values from Elements

Resource. |ElementName|: gets the value of the element

Like in:

status = obs.status;

if (|ElementName|) true if the element has any defined value

if (obs.status) {...something...};

Let's review how to populate elements for some of the more commonly used data types in FHIR by populating a patient resource.

This is all the demographic info we have about our patient:

Let's put together these two resources (Patient & Observation) to demonstrate how to use the most common FHIR data types in fhir-kit-client. There are no explicit data types, so you are on your own validating! You don't need to name the elements or use specific methods, all you have to do is populate the elements... just like writing in Postman :-)

Example #S -12: Populate Patient and Observation

Note: The complete example is available in the ma_e12.js file.

Person Names

p.name= [{
        use: "official",
        family: "Everywoman",
        given: [
            "Eve"
        ],
        prefix: [
            "Ms."
        ],
        suffix: [
            "III"
        ]
    }];

Addresses (Address)

p.address= [{
        line: [
            "9999 Patient Street"
        ],
        city: "Ann Arbor",
        state: "MI",
        postalCode: "90210",
        country: "USA"
    }];

Dates (Date Type)

p.birthDate= "1968-07-23";

Date/Time /Instant

p.deceasedDateTime= "2019-02-13T10:30:00-03:00";

Identifiers (identifier)

 p.identifier=[{
            use: "official",
            system: "http://citizens-id.gov/citizens",
            value: "69999999-I"
        },
        {
            system: "http://hospital.gov/patients",
            value: "9999999"
        }
    ]           

Attachments (attachment)

p.photo= [{
        contentType: "application/jpeg",
        data: "aVZCT1J3MEtHZ29BQUFBTlNVaEVVZ0FBQUFFQUFBQUJDQVlBQUFBZkZjU0pBQUFBQzBsRVFWUjRuR05nQUFJQUFBVUFBWHBlcXo4PQ=="
    }];

Coded Values (Codeable Concepts) / Simple Codes – (Enumeration)

p.communication= [{
            "language": {
                "coding": [{
                    "system": "urn:ietf:bcp:47",
                    "code": "en-US"
                }]
            },
            "preferred": true
        },
        {
            "language": {
                "coding": [{
                    "system": "urn:ietf:bcp:47",
                    "code": "en-US"
                }]
            };
            "preferred": false
        }
    ];  

p.maritalStatus= {
        coding: [{
            system: "http://terminology.hl7.org/CodeSystem/v3-MaritalStatus",
            code: "W"
        }]
    };
 
 p.gender= "female";
 

Telecom (phone, email addresses)

p.telecom= [{
            system: "phone",
            value: "(777) 555-9999"
        },
        {
            system: "email",
            value: "eve@everywoman.com"
        }
    ];

Active (boolean)

p.active= false;

Quantities (quantity)

obs.valueQuantity = {
                       value: 6.3,
                       unit: "mmol/L",
                      system: "http://unitsofmeasure.org/",
                      code: "mmol/L"};

Reference to a Local Resource (reference)

obs.subject={ display=p.name[0].family, reference="Patient/"+p.id};

Full Reference to a Logical Resource Id (reference)

obs.subject={ display="Other server patient", reference="http://otherserver.com/fhir/Patient/1234";

Narrative

FHIR-KIT-CLIENT does not provide any automated approach for narrative generation.

So you will be involved in manually completing the narrative ("text" element) of the resource.

You need to define which elements are relevant (we recommend including at least the "summary" elements – marked as such in the FHIR spec for the resource with an "Σ" symbol), and concatenate all the element's value with a human-readable form of the element name.

Manually completing the narrative (plain text or html):

var DivNarrative =
    "<div xmlns='http://www.w3.org/1999/xhtml'>" +
    "Name:" + p.name[0].family + "," + p.name[0].given[0] + "<br/>" +
    "Identifier:" + p.identifier[0].system + "-" + p.identifier[0].value + "<br/>" +
    "Gender:" + p.gender + "<br/>" +
    "BirthDate:" + p.birthDate + "<br/>" +
    "Active:" + p.active + "<br/>" +
    "Managing Org:" + p.managingOrganization.display + "<br/>" +
    "Address:" + p.address[0].line[0] + " " +
    p.address[0].state + " " +
    p.address[0].city + " (" +
    p.address[0].postalCode + ") " +
    p.address[0].country +
    "<br/>" +
    "Telecom:" + p.telecom[0].System + "-" + p.telecom[0].Value + "<br/>" +
    "</div>";

p.text = {
    status: "generated",
    div: DivNarrative
};

Micro Assignment #S-15: Populating FHIR resources

Create a program to create a patient resource and its related lab result resource with the information provided below:

There are no specific methods provided for the Fhir-kit-client to access elements inside of FHIR resources.

However, as we saw in the previous sections, you can access ("get" and "set") elements inside of FHIR resources directly, treating them as what they are – plain JS objects.

However, you are responsible for the contents and you will not be able to leverage "smart" editors or content validation unless you use the above-mentioned TypeScript library.

For repeating values (example: you can add more than one identifier to a patient), you can use |ElementName|.push(|content|)).

As we discussed before, the syntax in js to populate a resource is basically as follows:

|MyResourceName|.|elementName|=|ElementValue|; 
|MyResourceName|.|elementName|.push(|ElementValue|)  

Depending on the specific element, these |elementName| .. |elementValue| elements can be objects of

  1. Simple or "primitive" types (string, date, etc.). In FHIR there are not really "primitive" types because every data type can be extended!
  2. complex FHIR data types
  3. FHIR backbone elements – complex elements composed by other elements, and defined by the FHIR spec

d) Choices ( |Element(x)| )

The element names are taken verbatim from the spec; there is no "artificial" method.

Note: We mention "artificial" methods because when using other libraries you can access a function called object.getStatus to access the status element, while in js you just do object.status. The same applies no matter how convoluted the element is.

Just to clarify these four variations, let's review some examples, using the Condition resource.

Straining to read the small print in this picture (we are getting older...), let's review some of the elements using this logic.

Note: Another option is to follow this example using the web page

https://www.hl7.org/fhir/condition.html instead of consulting the image below.

Let's try to dissect how to treat the different elements of this resource:

  1. Primitive data types are allowed directly. Be very careful with date/time formats, only JSON format is allowed: YYYY-MM-DDTHH:NN:SSZOH:OM.
  2. For those elements with single cardinality (0..1 or 1..1), like clinicalStatus, verificationStatus, or subject, you will use elementName=ElementValue,

with elementName= the "Name" in the first column and ElementValue instantiated based on the "Type" in the fourth column.

Example #S-13: Single Cardinality

cnd.clinicalStatus = { //but coding MAY repeat so it's an array
        coding: [{
            system: "http://terminology.hl7.org/CodeSystem/condition-clinical",
            code: "active"
        }]
    };
  1. For those elements with multiple cardinalities (1..* or 0..*), like identifier, bodySite, or category, you will use elementName.push(ElementValue), with elementName= the "Name" in the first column, and ElementValue instantiated based on the "Type" in the fourth column, or just write [{elementValue}].

Let's review a complex type as an example. This element: stage not only can repeat but also is not defined using a FHIR DataType, but as BackboneElement, a combination of FHIR elements: summary (CodeableConcept), assessment (reference to specific resource classes: ClinicalImpression, DiagnosticReport, or Observation), and type (CodeableConcept).

Example #S-14: Populating Elements in Resources

cnd.stage = [];
cnd.stage.push({
    //0..1: type may NOT repeat so we will include a single element
    type: { //but coding MAY repeat so it's an array
        coding: [{
            system: "http://snomed.info/sct",
            code: "422375001"
        }]
    },
    //0..1: summary may NOT repeat so we will include a single element
    summary: { //but coding MAY repeat so it's an array
        coding: [{
            system: "http://snomed.info/sct",
            code: "385356007"
        }]
    },
    //assessment may repeat so we include an array
    assessment: [{ reference: "DiagnosticReport/201993" }]
});
  1. For elements that are "choices," the element needs to be created with one of the valid data type or element choices.

One example in the Condition resource is the "onset" element, defined as a choice between Age, DateTime, Period, Range and String.

Example #S-15: Populating Resources: "Choices"

cnd.onsetAge = {
    value: 18,
    code: "Y",
    unit: "Y"
};

Micro Assignment #S-16: Populate a Resource

Create a program to populate an AllergyIntolerance resource with the following information:

ClinicalStatus: active

Type: allergy

Category: food

Criticality: high

Patient: Patient/49293

Code: 91935009 (http://hl7.org/fhir/ValueSet/substance-code): Allergy to peanuts

onset: age 10.

Introduction

Here we will present you with some standard JS methods to extract values from FHIR resources and elements.

  1. To test whether an element is valued: if (instance.|elementName|)

This also applies to choice elements.

  1. To extract the value for a specific element: instance.|elementName|
  1. To get a specific item of a repeating element: instance. |elementName|.[x] x=0...n
  1. To test whether an element or resource is empty: if !(element)
  1. To get the narrative for a resource instance.text.div

Examples for these methods

We will use a FHIR IPS-condition example to extract certain values:

Get the code and system for the ClinicalStatus

  1. To test whether an element is valued:
if (c.clinicalStatus!=null)
  1. To extract the system for the first coding for the clinical status:
Result=c.clinicalStatus.coding[0].code + "-" + c.clinicalStatus.coding[0].system

Example #S-17: Extracting Information from Instances: "Getters"

//The element is populated
var result="No Clinical Status";
if (cnd.clinicalStatus) {
    //And the coding is also populated 
    if (cnd.clinicalStatus.coding) {
        //And cardinality is at least what I am searching for
        if (cnd.clinicalStatus.coding.length > 0) {
            //Then I extract the value
            result = cnd.clinicalStatus.coding[0].code +
                " (" + cnd.clinicalStatus.coding[0].system + ")";
            console.log("Clinical Status Retrieved");
            console.log(result);
        }
    }
};

Example #S-18: Choice Element to Specific Data Type

//We need to get the value of the Condition.onset in one string to show it to the user
//onset is a choice so we can extract one of: Age, DateTime, Period, Range, String
var onsetValue = "-";
var onsetType = "No Value"
if (cnd.onsetAge) {
    onsetType = "Age";
    onsetValue = cnd.onsetAge.value + " " + cnd.onsetAge.unit;
} else {
    if (cnd.onsetDateTime) {
        onsetType = "DateTime";
        onsetValue = cnd.onsetDateTime;
    } else
    if (cnd.onsetPeriod) {
        onsetType = "Period";
        onsetValue = cnd.onsetPeriod.start;
        if (cnd.onsetPeriod.end)
            onsetValue = onsetValue + "-" + cnd.onsetPeriod.start;

    } else {
        if (cnd.onsetRange) {
            onsetType = "Range";
            onsetValue = cnd.onsetRange.low.value + " " + cnd.onsetRange.low.unit;
            if (cnd.onsetRange.high) { onsetValue = cnd.onsetRange.high.value + " " + cnd.onsetRange.high.unit; }

        } else {
            if (cnd.onsetString) {
                onsetType = "String";
                onsetValue = cnd.onsetString;
            }
        }
    }
};
console.log("Condition On Set Type:" + onsetType);
console.log("Condigion On Set Value:" + onsetValue);

Micro Assignment

Micro Assignment #S-17: Extract information from an IPS Observation

Create a program to extract the following information from an IPS Observation in OBSERVATION.XML: text – status – category – code – date/time - value.

To install FHIR for node.js, execute

npm install fhir

To parse this XML resource into a JS object, use the following snippet:

//Code to read the observation file
const fs = require('fs');
var path = require('path');
var resourcePath = path.join(__dirname, '.', 'FHIR_RESOURCES', 'OBSERVATION.XML');
var contents = fs.readFileSync(resourcePath, 'utf8');
//Code to parse the FHIR XML into a js object
var Fhir = require('fhir').Fhir;
var fhir = new Fhir();
var ob = fhir.xmlToObj(contents);

More details on Lantana's FHIR npm module can be found here: https://www.npmjs.com/package/fhir

Introduction

In this section we will discuss how to:

  1. Populate simple extensions
  2. Assemble complex extensions
  3. Add extensions to your resource
  4. Add extensions to any element in your resource

Populating a simple extension

Just create a new object with a URL and a value element named as described in https://www.hl7.org/fhir/extensibility.html

Recipe # S-15: Populating a Simple Extension

var ext = {
        url:'|ExtensionURL|'
         ,value|ExtensionType|:'|ExtensionTypeValue|'};

|ExtensionURL|

The URL defining the extension

|ExtensionType|

Data type for the extension value

|ExtensionTypeValue>

Value for each element in the data type

Allowed extension data types should be described in the extension definition in the implementation guide, and limited to the data types described here: https://www.hl7.org/fhir/extensibility.html.

Example #S-19: Populate a simple IPS Extension

We will populate the IPS-defined extension for Allergy Abatement DateTime.

var ext = { url:'http://hl7.org/fhir/uv/ips/StructureDefinition/abatement-dateTime-uv-ips'
         ,valueDateTime:'2018-02-02'};

Populating complex extensions

To populate complex extensions, you need to first create the extension (only the URL), and then add all the simple extensions.

Recipe # S-16: Populate a Complex Extension

var extc1 = 
     { url : "|ComplexExtensionURL|",
      extension : [
        {
          url : "|LocalExtensionURL_1|",
          |ExtensionType_n| : {
            |Extension_m_Type| : "Extension_m_Value",
            ...
          }
        },
        {
          url: "|LocalExtensionURL_n|",
            |Extension_m_Type| : "Extension_m_Value",
            ...
          }
        },...
      ]
     };

|ComplexExtensionURL|

The URL defining the complex extension

|LocalExtensionURL_n|

Local URL for the component of the complex extension

|ExtensionType_n|

Data type for the extension component value

|Extension_m_TypeValue_n>

Value for each element in the data type for the extension component

Example #S-20: Populate Complex Argonaut Extension

We will populate the Argonaut us-core-ethnicity extension for the Patient resource

var extc1 = 
     { url : "http://hl7.org/fhir/us/core/StructureDefinition/us-core-race",
      extension : [
        {
          url : "ombCategory",
          valueCoding : {
            system : "urn:oid:2.16.840.1.113883.6.238",
            code : "2135-2",
            display : "Hispanic or Latino"
          }
        },
        {
          url: "detailed",
          valueCoding : {
            system : "urn:oid:2.16.840.1.113883.6.238",
            code : "2180-0",
            display : "Dominican"
          }
        },
        {
            url:"text",
            valueString:"Hispanic or Latino"
        }
      ]
     };

Adding extensions to a resource or element

To add your extensions to a resource, just invoke the push method on the extension element of the resource.

Example #S-21: Adding an extension to a resource instance

MyPatient.extension=[];
MyPatient.extension.push(ext);

To add your extensions to any element in your resource, you need to invoke the |ResourceName|.|ElementName|.extension.push (|YourExtension|) method.

Example #S-22: Adding an extension to an element

Here we add the HL7 BirthTime extension to the birthDate element of the Patient resource:

var extBirthDT = {
    url: "http://hl7.org/fhir/StructureDefinition/patient-birthTime",
    valueDateTime: "1965-06-05T20:30:40"
};
MyPatient.birthDate="1965-06-06";
MyPatient.birthDate.extension = [];
MyPatient.birthDate.extension.push(extBirthDT);

Micro Assignment

Micro Assignment #S-18: Create a Patient resource with extensions

Create a patient resource with all the extensions required by the Argonaut IG.

Introduction

In this section, we will review methods to:

  1. Test if a specific extension (or any extension) exists in a resource or element, including extensions (!)
  2. Get a specific extension (by index)
  3. Get the URL for an extension
  4. Determine the value type for an extension
  5. Get the value for an extension

Test if a specific extension exists

To test if any specific extension exists: use if (element.extension). It will return false if the extension does not exist. element.extension.length will give you the number of extensions in a resource. To test if a specific extension exists you will need to go through the collection or use the filter method: if (element.extension.filter(url => url = "|ExtensionUrl|"));

Recipe # S-17: Test for the existence of extensions in a resource or element

if (|MyObject|.extension) {
    console.log("Exists");
    var q = |MyObject|.extension.length;
    console.log("Quantity");
    console.log(q);
    console.log("Going Over the Collection");
    const searchextension = "|ExtensionUrl|";
    for (let i = 0; i < |MyObject|.extension.length; i++) {
        var currentExt = |MyObject|.extension[i];
        if (currentExt.url == searchextension) {
            console.log(JSON.stringify(currentExt));
        }
    }
    console.log("Using Filter");
    found = |MyObject|.extension.filter(ext => { return (ext.url == searchextension); });
    console.log(JSON.stringify(found));
}

You need to replace:

|ExtensionUrl|

url defining the extension, if this parameter is not included, hasExtension returns true if the object has any extensions at all

|MyObject|

Any resource or extension (for complex extensions)

Get a Specific Extension by Index

To get a specific extension from an object (by index) use the Object.extension[x] method.

Example #S-23: Get all extensions from a resource/element

var ExtList = '';
    for (let i = 0; i < MyPatient.extension.length; i++) {
        var currentExt = MyPatient.extension[i];
        ExtList = ExtList + currentExt.url + '\r\n';
    }
    console.log(ExtList);

Get the URL and Value type for an extension

To get the url and value type for an extension, use the extension[x].url and delve into the extension object contents to extract the other element (value...)

Example #S-24: Get URL/Data Type for an extension

 var ExtList = '';
    for (let i = 0; i < MyPatient.extension.length; i++) {

        var currentExt = MyPatient.extension[i];
        var DataType = "";
        var url = currentExt.url;
        //Now we are investigating the extension to see which elements it has other than the url
        //This is feasible because extensions have an explicit typed value in FHIR

        for (var k in currentExt) {
            console.log(k);
            if (k != "url") { DataType = k; break; }

        }
        ExtList = ExtList + 'url:' + url + ' , ' + DataType + '\r\n';


    }
    console.log(ExtList);

Get the actual value from an extension

Here, it really depends on the extension value data type, not because JS is so picky on data types of things (believe us, it isn't), but because the actual name of the element is also the data type when dealing with FHIR extensions.

Example #S-25: Get the code from a Code type extension

//Now we are investigating the extension to see which elements it has other than the url
//This is feasible because extensions have an explicit typed value in FHIR
if (typeof currentExt.valueCode != "undefined") {
   var url = currentExt.url;
   var code = currentExt.valueCode;
   CodeList = CodeList + 'url:' + url + ' , code: ' + code + '\r\n';
}

Micro Assignment

Micro Assignment #S-19: Extract all coded data from a patient resource extension

The file PATIENT_EXTENSIONS.XML has an Argonaut patient resource with all the extensions fully populated. Extract the codes for the "coding" extensions for the complex extension with the URL http://hl7.org/fhir/us/core/StructureDefinition/us-core-race

In the following sections, we will consider only how to populate transaction and document bundles, which you will probably need to populate as a FHIR client.

The techniques we will show you will allow you to populate these artifacts when programming a FHIR-server as well.

Introduction

To populate transaction bundles as a client, you need to:

  1. Create the bundle and populate the header: at least: bundle type (transaction), bundle id (UUID)
  2. Add each entry, where each entry has:
  1. Execute the transaction to the server and read the transaction response.

Creating the Transaction Bundle

The only mandatory elements are the bundle type (transaction) and a new identifier for the bundle. We recommend using a GUID, by invoking UUID.

Remember to install UUIDfirst, by doing

npm install uuid

After installing UUID, you can require it in your solution using require(‘uuid') and invoke any of the GUID creating methods. See more details at https://www.npmjs.com/package/uuid

Example #S-26: Create a Transaction Bundle, Populate Header

uuid = require('uuid');
var MyBundle = new Object();
MyBundle.ResourceType = 'Bundle';
MyBundle.id = uuid.v4();

Adding the Entries to the Bundle

For each resource that you need to include, you need to add an entry.

The entry will include:

Example #S-27: Add Entry to a Bundle

//Note: PatientEntryId should point to the fullUrl of the Patient entry
var MyDevice = {
    resourceType: "Device",
    identifier: [{ system: "http://wwww.americandevices.com", value: "123456" }],
    deviceName: [{
        "name": "BLOOD PRESSURE MASTER"
    }],
    manufacturer: "AMERICAN BLOOD PRESSURE DEVICES",
    modelNumber: "2000 PLUS",
    serialNumber: "123456",
    patient: { reference: "Patient/"+PatientEntryId }

}
var DevToken = MyDevice.identifier[0].system + "|" + MyDevice.identifier[0].value;
var DevUrl = "Device?identifier=" + DevToken;


DeviceEntryId = uuid.v4();
var MyDeviceEntry = {
    fullUrl: DeviceEntryId,
    resource: MyDevice,
    request: {
        method: "PUT",
        url: DevUrl
    }
};

MyBundle.entry = [];
MyBundle.entry.push(MyDeviceEntry);

Executing the Bundle Transaction


Since our library does not support executing transactions, we need to resort to a general library (again, axios).

Remember that we need to post to the fullUrl of the server, with no resource specified (the server's "FHIR endpoint").

Example #S-28: Execute a Bundle Transaction

const axios = require('axios');
var urlFHIREndpoint = 'https://hl7-ips-server.hl7.org/fhir';
var FullURL = urlFHIREndpoint + "/";
     
    axios.post(FullURL, MyBundle)
    .then(response => {
        //We check the response status
        var data = response.data; { console.log(JSON.stringify(data)); }
    })
    //Any other problem, we failed
    .catch(error => {
        console.log(JSON.stringify(error.response.data));
    });

Micro Assignment

Micro Assignment #S-20: Create a Transaction Bundle

Create a transaction bundle with a patient, device and the blood pressure readings (systolic and diastolic).

If you have any trouble with the transaction, i.e. error 422 because an identifier is repeated in our server, just change the patient or device identifier.value to something more unique.

Introduction:

Documents in FHIR are just a special kind of Bundle. To populate document bundles, you need to:

  1. First, create the bundle and populate the header: bundle.type (document)
  2. Add a first special entry, containing a resource element: Composition.
  3. Add other entries. Each entry has:
  1. A fullUrl element, with a UUID identifying the resource inside the transaction
  2. A resource element, with the actual resource. Remember that references inside of the transaction point to other entries' fullUrl.

You will need entries to include all the other resources: Patient for the subject, Practitioner for the author, and all the sections' referred resources.

d) Post the document bundle to the server and read the server response.

Full Example

Example #S-31: Create an IPS Document Bundle

Note: This example is too long to include here, so you can download it from the gist link above.

Micro Assignment

Micro Assignment #S-22: Add an IPS-conformant immunization section and entry to our IPS bundle

Begin with our example above, and modify it to add an immunization section to the composition and the corresponding immunization entry (conformant to the IPS).

Introduction

You can process each resource in a document bundle using the entry element (remember that a document is just a list of entries).

For each entry you can extract the actual resource using entry.resource and the full url using entry.fullUrl. You can also get the resource type using entry.resource.resourceType.

Example

Example #S-32: Process all entries in a bundle

//doc has the full bundle
//This will get you the patient name and identifier - just the first one
var entries = doc.entry;
entries.forEach(entry => {
    if (entry.resource.resourceType == "Patient") {
        var pat = entry.resource;
        PatientName = pat.name[0].family + "," + pat.name[0].given[0];
        PatientIdentifier = pat.identifier[0].system + ":" + pat.identifier[0].value;
       }
}

Micro Assignment

Micro Assignment #S-23: Extract elements from ips document (ips_document.json)

Note 1: Extract just the Patient Name, Identifier and Condition.code

Note 2: In order to read the file and parse the contents into a JSON object, you can use the following code:

const fs = require('fs');
var path = require('path');
var resourcePath = path.join(__dirname, '.', 'FHIR_RESOURCES', 'IPS_DOCUMENT.JSON');
var contents = fs.readFileSync(resourcePath, 'utf8');
//Code to parse the FHIR JSON into a js object
var doc = JSON.parse(contents);

Introduction

In order to include resources as "contained," the easiest way is to use this three-step method:

  1. Populate the Id element of the contained resource with a unique string valid for XML reference (IDREF). The string needs to be unique for the container resource.
  1. Initialize the contained element of the container (if needed), and then add the contained element to "contained" element of the container resource.
  2. Set the reference in the container resource referencing element to the IDREF defined in step 1.

Scratching your head? Don't worry, it's a lot easier to show you than to try to explain.

Recipe and Example

Recipe # S-18: Include contained resources

var MyContained= {
    resourceType: "SomeOtherResourceType",
    //This is step 1: creating an internal XML IDREF for the contained resource
    id: "MyContainedId"
    //Other elements will surely follow
};
var MyContainer = {
    resourceType: "SomeResourceType",
    //This is step 2: including the contained resource in the container's contained collection
    contained: [MyContained],
    //This is step 3: referencing the contained resource by invoking # and its IDREF
    elementReferencing: { reference: "#" + MyContained.id}
    //Other elements should follow
    
};

Example #S-29: Add a Contained Resource to an Observation

//Device creating the observations: 
var MyDevice = {
    resourceType: "Device",
    //This is step 1: creating an internal XML IDREF for the contained resource
    id: "MyDevice",
    identifier: [{ system: "http://wwww.americandevices.com", value: "123456" }],
    deviceName: [{
        "name": "BLOOD PRESSURE MASTER"
    }],
    manufacturer: "AMERICAN BLOOD PRESSURE DEVICES",
    modelNumber: "2000 PLUS",
    serialNumber: "123456",
    patient: { reference: "Patient/XXXX", display: "The Patient" }

}

var MyObs = {
    resourceType: "Observation",
    //This is step 2: including the contained resource in the container's contained collection
    contained: [MyDevice],
    issued: JSON.stringify(Date),
    ValueQuantity: { value: 120.0, unit: "mmHG", code: "mmHG", system: "http://unitsofmeasure.org" },
    code: { coding: { system: "http://loinc.org", code: "12345-1", display: "SOME LOINC CODE" } },
    subject: { reference: "Patient/XXXX", display: "Some Patient" },
    //This is step 3: referencing the contained resource by invoking # and its IDREF
    device: { reference: "#" + MyDevice.id, display: MyDevice.deviceName.name }
}

console.log(JSON.stringify(MyObs));

Micro Assignment

Micro Assignment #S- 21: Add a contained asserter element to an allergy-intolerance resource.

Introduction

In order to extract contained resources you will need to use two methods:

  1. The contained element is an array with all the contained resources, so you can use resource.contained[i] to get any specific resource from the array. i: 0...resource.contained.length
  2. You can also do a filter by resourceType and contained.id element

Example

Example #S-30: Get the Contained Device's Name andType

/

/MyObs is an Observation resource containing a Device resource, and referencing it through its device element using ID/IDREF
var FoundDevices = MyObs.contained.filter(x => x.resourceType == "Device" && "#" + x.id == MyObs.device.reference);
var device = FoundDevices[0];
if (device) {
    var DeviceName = device.deviceName[0].name;
    var DeviceManufacturer = device.manufacturer;
    console.log(DeviceName);
    console.log(DeviceManufacturer);

}

Introduction

When you have your resource content in string format, you will need to use a parser.

Recipe # S-19: Parsing a Resource

The options are:

For JSON content:

JSON parsing is native in node.js –remember that JSON is "JavaScript Object Notation"- just use:

myobject=JSON.parse(content)

For XML content:

As we mentioned for a previous MicroAssignment, you will need to resort to another library.

To install FHIR for node.js, execute

npm install fhir

To parse this XML resource into a JS object, use the following snippet:

//Code to parse the FHIR XML into a js object
var Fhir = require('fhir').Fhir;
var fhir = new Fhir();
var myobject = fhir.xmlToObj(contents);

More details on Lantana's FHIR npm module can be found here: https://www.npmjs.com/package/fhir

Example #S-33: Parsing a Document Bundle from a JSON file

const fs = require('fs');
var path = require('path');
var resourcePath = path.join(__dirname, '.', 'FHIR_RESOURCES', 'IPS_DOCUMENT.JSON');
var contents = fs.readFileSync(resourcePath, 'utf8');
var doc = JSON.parse(contents);

Micro Assignment #S-24: Read the PATIENT.XML file and parse it; print the patient name and identifier to the console.

Introduction

Fhir-kit-client does not provide either basic resource validation (we discussed how to achieve that already) or profile based resource validation.

Let's explore how to achieve the latter option using axios.

Another option is to use Lantana fhir.js library, as we did for XML parsing, but since we try to do as

little as possible with our clients to reduce complexity, let's explore server-based validation.

Profile Declaration

You need to set the value for the meta.profile element of your resource to the canonical url for the profile.

<meta>
  <profile 
             value="http://myorganization.org/StructureDefinition/myprofilename"/>
</meta>

In JS, this is achieved with this line of code:

resource.meta = { profile: ["http://myorganization.org/StructureDefinition/myprofilename"] };

Profile Based Resource Validation (Server based)

If you have one server with all the required profiles (StructureDefinitions), ValueSets and CodeSystems (example: for a given implementation guide), you can call the server $validate operation with the profile declared in the meta element of the resource.

Example #S-34: Executing $validate for a patient resource

var urlFHIREndpoint = 'https://hl7-ips-server.hl7.org/fhir';
var ResourceClass = "Patient";
var OperationName = "$validate";
var FullURL = urlFHIREndpoint + "/" + ResourceClass + "/" + OperationName;
//We call the FHIR endpoint with our parameters
axios.post(
        FullURL, contents, {
            headers: {
                "Content-Type": "application/fhir+xml",
                "Accept": "application/fhir+json"
            }
        }
    )
    .then(response => {
        //We check the response status
        var data = response.data; { console.log(JSON.stringify(data)); }
    })
    //Any other problem, we failed
    .catch(error => {
        var message = error;
        console.log(message.response.data.text);
    });

Micro Assignment #S-25: Use $validate to assess an instance conformance to US-CORE

Validate an instance of Patient using the $validate operation against a FHIR server.

FHIR server: https://hl7-ips-server.hl7.org/fhir/

Profile canonical URL: http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient

Instance: Located in the PATIENT_EXTENSIONS.XML file

Introduction

Serializing an object from a string to JSON in node.js is a native operation. To achieve the same thing for XML, we need to resort to a FHIR-aware external library.

Recipe # S-20: Serializing Resources

The options are:

To serialize into JSON:

Remember that JSON is "JavaScript Object Notation", so our "in-memory" objects are almost in JSON already. Just use the JSON.stringify method.

mycontent=JSON.stringify(resource)

To serialize into XML:

As we mentioned for a previous MicroAssignment, you will need to resort to another library.

To install FHIR for node.js, execute

npm install fhir

To parse this XML resource into a JS object, use the following snippet:

//Code to parse the FHIR XML into a js object
var Fhir = require('fhir').Fhir;
var fhir = new Fhir();
var myobject = fhir.objtoXml(resource);

More details on Lantana's FHIR npm module can be found here: https://www.npmjs.com/package/fhir

Example #S-35: Serializing a Practitioner resource into JSON format

We will populate the resource in a not too fluent way (element by element), but the outcome will be our old JSON-format FHIR object anyway.

var newPractitioner = new Object();
newPractitioner.resourceType = "Practitioner";
newPractitioner.identifier = [{ system: "(http://canada.gov/cpn", value: "51922" }];
newPractitioner.active = true;
newPractitioner.name = [{ family: "Dellacroix", given: ["Madeleine"] }];
newPractitioner.telecom = [{ system: "phone", value: "613-555-0192" },
    { system: "email", value: "qcpamxms9dq@groupbuff.com " }
];
newPractitioner.address = [{ line: ["3766 Papineau Avenue"], city: "Montreal", state: "Quebec", postalCode: "H2K 4J5", country: "Canada" }];
newPractitioner.qualification = [{ code: { coding: [{ system: "http://canada.gov/cpnq", code: "OB/GYN" }] } }];
console.log(JSON.stringify(newPractitioner));

Introduction

For JSON, pretty print is implemented natively in JSON.stringify, and there are two optional parameters for the method.

You can explore them at https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify

Option

Description

replacer

Array or function to filter specific elements based on type or name

space

Number or string defining the quantity or characters for the indenting

For XML, there are no options detailed in Lantana's FHIR.js library.

Recipe # S-21: Options for Resource Serialization (stringify)

Options are specified when invoking the stringify method like this:

console.log(JSON.stringify(|ResourceInstance|, ['|element_name_1|', '|element_name_2|', '|element_name_3|'], |spaces|));

You need to replace:

|ResourceInstance|

The resource instance

|spaces|

Quantity of spaces for indentation in pretty print

|element_name_1|...|element_name_n|

Names of elements to include in the serialization. You need to express ALL names you want to include. Including an element does not guarantee inclusion of its children elements.

Micro Assignment #S-26: Use serialization options

Use the appropriate options to serialize the Practitioner example from the previous page, omitting the narrative, using only the patient name, with pretty print indentation of two spaces.

Introduction

We need to explore several techniques related to searching in fhir-kit-client.

In the following sections, we will review:

Introduction

Using fhir-kit-client, you can express the search query by criteria: just include the criteria search parameters using an object array.

The first argument must be by the resource type, but the syntax for the search parameters is the same you would use when writing the search manually. The fhir-kit-client object reference would not be helping you to write it or validate it.

Example #S-36: Search By Criteria

fhirClient
    .search({ resourceType: 'PractitionerRole', searchParams: { specialty: 'http://nucc.org/provider-taxonomy|208D00000X' } })
    .then((response) => {
        console.log(JSON.stringify(response, null, 2));
        return response;
    })
    .catch((error) => {
        console.error(error);
    });

For some more options using the search method, just review the search section of the FHIR spec (https://www.hl7.org/fhir/search.html) or the search section for each resource in the specific Implementation Guide you are using.

The result will be returned in a Search results bundle.

We discussed in previous sections how to retrieve all the entries in a Document bundle.

You can use the entry Element to process the returned results.

Example #S-38: Processing search results

In this example we just go through the entries in the bundle.

fhirClient
    .search({ resourceType: 'Practitioner', searchParams: { "address-city": 'Boston' } })
    .then((response) => {
        entries = response.entry;
        entries.forEach(entry => {
            var pra = entry.resource;
            console.log(pra.name[0].family + ',' + pra.name[0].given[0]);
        })
    })

To implement paging you need to use the continue method:

  1. If the current bundle has a link element pointing to the next page, use the fhirClient.nextPage method to retrieve the next page.
  2. If the current bundle has a link element pointing to the previous page, use the fhirClient.prevPage method to retrieve the previous page.

Example #S-39: Search with paging

fhirClient
    .search({ resourceType: 'Patient', searchParams: { _count: '10', name: 'Smith' } })
    .then((response) => {
        console.log(response);
        return response;
    })
    .then((response) => {
        console.log(response);
        return fhirClient.nextPage(response);
    })
    .then((response) => {
        console.log(response);
        return fhirClient.prevPage(response);
    })
    .catch((error) => {
        console.error(error);
    });

Example #S-40: Search Practitioners

Note: This example is available if you follow the link to the gist above.

Micro Assignment #S-27: Patient search by demographic data and identifier

  1. Search for all patients with last (family) name exactly = "Smith," gender=male, born after 05-May-1965
  2. Search for the patient with identifier [system] http://hospital.gov/patients [value] 9999999 (Medical Record Number)

In order to encourage you to actually perform the micro assignments, the solutions are not included in each section but grouped here, so if you choose to read the solution instead of working it out, you can find all the solutions and examples here:

https://www.dropbox.com/sh/107yp3qe841b06l/AADgmQ4Sx05XMPmnTq72heGja?dl=0

This week we will have formal "programming" assignments .

We will ask you to change or enhance some programs using the fhir-kit-client client.

You can choose between two tracks: Argonaut and IPS.

In the Argonaut track, you will consume and create Argonaut-compatible resources, and search FHIR servers for patient demographics, provider information and terminology using the fhir-kit-client.

In the IPS track, you will consume IPS documents and show some of the underlying resources using the fhir-kit-client.

FHIR is just a specification. In order to do real work, we need to use our desired platform to program our access to FHIR servers.

The goal of this unit was for you to grasp common concepts that apply to all clients in FHIR and then apply them to the most used platforms/languages (Java, C#, JS).

Information about FHIR

There are a number of places where you can get information about FHIR.

Page