National Institutes of Health (NIH), Office of the Director (OD), Office of Data Science Strategy (ODSS)
June 22, 2023
FHIR® is the registered trademark of Health Level Seven International (HL7). Use of the FHIR trademark does not constitute an HL7 endorsement of this workshop.
The goal of the original SMART on FHIR API is audacious and can be expressed concisely: an innovative app developer can write an app once and expect that it will run anywhere in the health care system.
…
SMART provides a full stack of open specifications that enable a medical apps platform.
SMART stands for “Substitutable Medical Apps, Reusable Technology”, a standard by the SMART Health IT group
Based on open standards: FHIR, OAuth2, OpenID Connect
Widely implemented/used (e.g., on all iPhones)
Required as part of ONC certification for 21st Century Cures Act: §170.315(g)(10) Standardized API for patient and population services
SMART on FHIR lets you:
It ties together existing common web standards and HL7 specifications to enable secure EHR integration:
patient-view
hook is triggered when the patient record is opened, which could then call natural language processing softwarehttp://localhost:3000
for youlaunch.html
page into “App’s Launch URL” and click “Launch”Link to video: https://purl.org/fhir-for-research/workshops/smart-on-fhir/flow-video
You can copy file contents from https://purl.org/fhir-for-research/workshops/smart-on-fhir/sample-app-git.
If you cloned the repository this is already done.
SMART on FHIR walkthrough
Your browser (a client) gets redirected by the EHR to your app’s http://localhost:3000/launch.html with the following parameters:
iss
: Identifies the EHR’s endpoint for the applaunch
: An opaque identifier for this specific app launch and EHR context, required for security purposes
SMART on FHIR walkthrough
launch.html
executes an authorization request with select parametersSMART on FHIR walkthrough
launch.html
executes an authorization request with select parameters<script>
FHIR.oauth2.authorize({
// The client_id that you should have obtained after registering a client at
// the EHR.
//
// Note that this can be an arbitrary string when testing with
// http://launch.smarthealthit.org.
clientId: "my_web_app",
// The scopes that you request from the EHR. In this case we want to:
// launch - Get the launch context
// openid & fhirUser - Get the current user
// patient/*.read - Read patient data
scope: "launch openid fhirUser patient/*.read",
// Typically, if your redirectUri points to the root of the current directory
// (where the launchUri is), you can omit this option because the default value is
// ".". However, some servers do not support directory indexes so "." and "./"
// will not automatically map to the "index.html" file in that directory.
redirectUri: "index.html"
});
</script>
SMART on FHIR walkthrough
The clientId
parameter is a specific string obtained after registering the app in the EHR manually. Replace "my_web_app"
with your specific app identifier.
<script>
FHIR.oauth2.authorize({
// The client_id that you should have obtained after registering a client at
// the EHR.
//
// Note that this can be an arbitrary string when testing with
// http://launch.smarthealthit.org.
clientId: "my_web_app",
// The scopes that you request from the EHR. In this case we want to:
// launch - Get the launch context
// openid & fhirUser - Get the current user
// patient/*.read - Read patient data
scope: "launch openid fhirUser patient/*.read",
// Typically, if your redirectUri points to the root of the current directory
// (where the launchUri is), you can omit this option because the default value is
// ".". However, some servers do not support directory indexes so "." and "./"
// will not automatically map to the "index.html" file in that directory.
redirectUri: "index.html"
});
</script>
SMART on FHIR walkthrough
The scope
parameter specifies what kinds of data the app needs access to. See SMART on FHIR scope and lunch context for more data access options.
<script>
FHIR.oauth2.authorize({
// The client_id that you should have obtained after registering a client at
// the EHR.
//
// Note that this can be an arbitrary string when testing with
// http://launch.smarthealthit.org.
clientId: "my_web_app",
// The scopes that you request from the EHR. In this case we want to:
// launch - Get the launch context
// openid & fhirUser - Get the current user
// patient/*.read - Read patient data
scope: "launch openid fhirUser patient/*.read",
// Typically, if your redirectUri points to the root of the current directory
// (where the launchUri is), you can omit this option because the default value is
// ".". However, some servers do not support directory indexes so "." and "./"
// will not automatically map to the "index.html" file in that directory.
redirectUri: "index.html"
});
</script>
SMART on FHIR walkthrough
redirectUri
is where the EHR will redirect the web browser (client) to after authorization. In this case it is the app’s index.html
.
<script>
FHIR.oauth2.authorize({
// The client_id that you should have obtained after registering a client at
// the EHR.
//
// Note that this can be an arbitrary string when testing with
// http://launch.smarthealthit.org.
clientId: "my_web_app",
// The scopes that you request from the EHR. In this case we want to:
// launch - Get the launch context
// openid & fhirUser - Get the current user
// patient/*.read - Read patient data
scope: "launch openid fhirUser patient/*.read",
// Typically, if your redirectUri points to the root of the current directory
// (where the launchUri is), you can omit this option because the default value is
// ".". However, some servers do not support directory indexes so "." and "./"
// will not automatically map to the "index.html" file in that directory.
redirectUri: "index.html"
});
</script>
SMART on FHIR walkthrough
The demo EHR had you select a provider and patient in this phase. In the real world an EHR may already pull this information from context, or show another screen specifically asking a patient to give permission for access.
SMART on FHIR walkthrough
index.html
As requested earlier in your redirectUri
parameter.
SMART on FHIR walkthrough
FHIR.oauth2.ready()
This access token gets embedded in a client
object to authenticate and authorize future FHIR queries.
<script type="text/javascript">
FHIR.oauth2.ready().then(function(client) {
// Render the current patient (or any error)
client.patient.read().then(
function(pt) {
document.getElementById("patient").innerText = JSON.stringify(pt, null, 4);
},
function(error) {
document.getElementById("patient").innerText = error.stack;
}
);
// Get MedicationRequests for the selected patient
client.request("/MedicationRequest?patient=" + client.patient.id, {
resolveReferences: [ "medicationReference" ],
graph: true
})
// Reject if no MedicationRequests are found
.then(function(data) {
if (!data.entry || !data.entry.length) {
throw new Error("No medications found for the selected patient");
}
return data.entry;
})
// Render the current patient's medications (or any error)
.then(
function(meds) {
document.getElementById("meds").innerText = JSON.stringify(meds, null, 4);
},
function(error) {
document.getElementById("meds").innerText = error.stack;
}
);
}).catch(console.error);
</script>
SMART on FHIR walkthrough
…and writes the raw JSON data in the app’s patient box. A real world application should parse the JSON into something more useful.
<script type="text/javascript">
FHIR.oauth2.ready().then(function(client) {
// Render the current patient (or any error)
client.patient.read().then(
function(pt) {
document.getElementById("patient").innerText = JSON.stringify(pt, null, 4);
},
function(error) {
document.getElementById("patient").innerText = error.stack;
}
);
// Get MedicationRequests for the selected patient
client.request("/MedicationRequest?patient=" + client.patient.id, {
resolveReferences: [ "medicationReference" ],
graph: true
})
// Reject if no MedicationRequests are found
.then(function(data) {
if (!data.entry || !data.entry.length) {
throw new Error("No medications found for the selected patient");
}
return data.entry;
})
// Render the current patient's medications (or any error)
.then(
function(meds) {
document.getElementById("meds").innerText = JSON.stringify(meds, null, 4);
},
function(error) {
document.getElementById("meds").innerText = error.stack;
}
);
}).catch(console.error);
</script>
SMART on FHIR walkthrough
…and later writes the raw data in the app’s medication box.
<script type="text/javascript">
FHIR.oauth2.ready().then(function(client) {
// Render the current patient (or any error)
client.patient.read().then(
function(pt) {
document.getElementById("patient").innerText = JSON.stringify(pt, null, 4);
},
function(error) {
document.getElementById("patient").innerText = error.stack;
}
);
// Get MedicationRequests for the selected patient
client.request("/MedicationRequest?patient=" + client.patient.id, {
resolveReferences: [ "medicationReference" ],
graph: true
})
// Reject if no MedicationRequests are found
.then(function(data) {
if (!data.entry || !data.entry.length) {
throw new Error("No medications found for the selected patient");
}
return data.entry;
})
// Render the current patient's medications (or any error)
.then(
function(meds) {
document.getElementById("meds").innerText = JSON.stringify(meds, null, 4);
},
function(error) {
document.getElementById("meds").innerText = error.stack;
}
);
}).catch(console.error);
</script>
Generally speaking the pattern for a RESTful GET query appended to a URL will take the form of:
VERB [base]/[Resource] {?param=[value]}
https://api.logicahealth.org/FHIRResearchSynthea/open
GET [base]/Patient/1234
retrieves an instance of the Patient resourceMedicationRequest.subject
GET [base]/MedicationRequest?subject=1234
will get the instances of MedicationRequest for Patient/1234
MedicationRequest.subject
has a reference back to Patient, allowing us to retrieve instances if we know the patient’s IDGET [base]/Patient?name=peter
, and then a second to get the MedicationRequests for patients with that IDGET [base]/MedicationRequest?subject.name=peter
MedicationRequest.subject
can be either a Patient or Group, so this is better: GET [base]/MedicationRequest?subject:Patient.name=peter
What about “patients diagnosed with a given condition”?
Condition.subject
_has
parameter supports retrieving Patients based on a value from a Condition
:
separates fieldsCondition
)subject
)code
, which Condition uses to identify the condition)GET [base]/Patient?_has:Condition:subject:code=195662009
AND
to find john smith
: GET [base]/Patient?given=john&family=smith
OR
to find john smith
or jenny smith
: GET [base]/Patient?given=john,jenny&family=smith
FHIRPath is a path based navigation and extraction language, somewhat like XPath
Useful for extracting data from FHIR’s deeply nested data structure
JavaScript implementation: https://github.com/HL7/fhirpath.js
Try in the sandbox: https://hl7.github.io/fhirpath.js/
Get the value from Patient.gender
: Patient.gender
Get a patient’s legal last name: Patient.name.where(use='official').family
Get a patient’s MRN:
Patient.identifier.where(type.coding.system = 'http://hl7.org/fhir/v2/0203' and type.coding.code = 'MR').value
<iframe>
to show inline as well – it’s the same mechanism© 2023 The MITRE Corporation / Approved for Public Release / Case #23-0966