October 4, 2022
Work SMARTer, Not Harder with FHIR
Improve the likelihood of your healthcare app being featured in the SMART App Gallery—and used by more providers and patients—by taking advantage of SMART on HL7® FHIR®. Not only will you save time developing and maintaining your app, but you'll delight your users with a smoother, richer experience.
After all, you've got big goals for your healthcare app and user experience should play a big role in them.
If you're familiar with building apps that use OAuth 2.0 and OIDC, then you're already very close to understanding how to build a SMART on FHIR app.
First, let's cover a few of the SMART on FHIR basics, including OAuth2.0, OIDC, Scopes and Tokens before moving on to best practices. Already a pro on these topics? Feel free to skip this section.
SMART + FHIR = Interoperability x 1000
SMART stands for Substitutable Medical Apps, Reusable Technology. It’s defined as "open-source, standards-based API that leverages the OAuth 2.0 standard to provide secure, universal access to EHRs." It provides a universal API, so that any technology built with SMART works with any EHR database that uses SMART.
The SMART platform builds on the existing Fast Health Interoperability Resources (hence the name “SMART on FHIR”). FHIR standardizes data. SMART standardizes access to that data. SMART on FHIR protects user privacy while still making their data accessible and usable. We've written extensively about both SMART and FHIR, so if you need to brush up on the specifics, check out these posts:
- SMART on FHIR: Using SMART on FHIR Combined with a FHIR-Based Clinical Data Repository
- The Urgent Need for HL7® FHIR® Adoption
OAuth2 + OIDC = Tokens, Tokens, Tokens
OAuth2.0, which stands for Open Authorization, is a standard that gives a website or app access to resources hosted by other web apps. It results in Access Tokens and Refresh Tokens.
-
OAuth2.0 does:
-
cover application security and permissions
-
allow application access
-
convey authorization decisions
-
-
OAuth2.0 does not:
-
cover user level permissions
-
assign user roles
-
describe a user
-
manage or enforce user permissions
-
OIDC, short for OpenID Connect, is an extension to OAuth2.0 that solves the problem of authenticating a user. It makes single sign-on possible for users when they provide an email address or social network to authenticate their identity. It matches enterprise security requirements and provides a seamless user experience.
Tokens are your friend!
Tokens are what applications use to make API requests. OAuth2.0 creates Access and Refresh tokens. OIDC creates ID tokens. An id_token is a JSON Web Token, or JWT. It encodes information about the user, including information such as claims, names and email addresses.
Curious to learn more about OIDC, OAuth2.0, and tokens? This article lays it all out for you.
Scoping It Out: How SMART connects with OIDC and OAuth2.0
SMART on FHIR further builds on OIDC. It describes the healthcare-specific authorization details that FHIR applications should adhere to. So if you can already do proper OIDC flow in your healthcare application, congratulations—the hardest part is behind you. Now, let's talk about what SMART and FHIR add to the mix, namely scopes.
SMART scopes are lists of identifiers that specify the access privileges that are being requested. OIDC has many built-in scope identifiers. The most important ones you'll need to know are launch/patient and fhirUser.
Launch/patient scope will get the resource ID of the patient using the app. It tells the OIDC server to add the logical resource ID as a claim in the access token. FhirUser is an optional scope. It's a stand-in for profile in the standard OIDC spec that helps you get an ID token.
Take Advantage of SMART on FHIR
Whether you're dealing with Patient data or Practitioner data, you won't need to write separate views or applications for the data to talk to each other. Imagine all the free time you'll have now that you won't need to separate their flows or build different apps. You'll have reusable APIs and consistent testing, which means you'll only have one codebase to manage.
For example, let's say you're building an application that helps track when a person needs an immunization or booster shot. Do they need the shot now or five months from now? Your app will help the patient and provider determine and track the answer. The app will include a clinical side to dispense immunization and an administrative side that includes care plans and questionnaires. The data models of both sides of the app are the same because they’re built on FHIR.
First, you'll build the patient record and questionnaire response. Then you'll include any observations that occurred. Next, the app will attach documents via document references. Because you used FHIR, the clinical view is loaded immediately, which generates a workflow.
Easy, right?
Gathering Data for Your SMART on FHIR Apps
How to Approach Queries
Building queries to efficiently capture the data you need is an art form. Make sure to use the right search parameters so you get the data you need. To bundle relevant data at once, use: _include, _revinclude, iterate
Implementation guides will change which searches you have access to. For example, "ExplanationOfBenefit" (EOB) doesn't have an insurer as a parameter in FHIR Core. However, CARIN BB does. Sometimes you'll need to use search parameters like _include and _revinclude. Other times, $everything will be more appropriate.
_include and _revinclude are standard search parameters in FHIR. You can modify their behavior to get only the data you want. Let's go back to our immunization example. With _include, you can get a patient's immunization record plus related records, including the provider.
$everything will give you information about the patient AND records related to the patient records. For example, you'll get patient records, EOB and provider. Use $everything when a user needs to send their complete medical records somewhere else, like to a different insurer. But otherwise, don't use $everything. The amount of data quickly adds up. As the user's medical history grows, the amount of data grows, which slows the response from the server. Using this query can overwhelm the device the person is using to access their data.
Ready to learn more about the FHIR $everything spec? Here's $everthing you need to know.
Finally, use FHIRPath for filtering results and writing expressions in your search parameters. This will help you match the information you're looking for with what exists in your data. You can write expressions that more succinctly target what you're filtering your data for.
What About GraphQL and Elements?
Let's say you need focused, large sets of data. Often, these data sets will be quite large because they contain information that doesn't relate to what you want to display. GraphQL is popular outside of FHIR when you need to get all the records and data about a user. FHIR enables you to use GraphQL to modify the data you're consuming. Filter elements in your query with GraphQL, which can flatten resources and modify the structure of your bundle so you can build more focused data sets.
GraphQL vs _elements
In FHIR, _elements is a search parameter that allows you to change what is returned from the API call. You can include or exclude any information that you specify.
Snapshot returns a JSON structure (a representation of your FHIR data) that you determine based on what you want to include. Snapshot can also exclude metadata. Use Snapshot to check specific structures in an element, such as questionnaire responses, as well as the cardinality of the resource elements you’re working with, such as determining if a field allows for multiple strings or one string.
Take a look at these before and after screenshots. See how much simpler they are to read?
Before: http://yourserver.com/fhir/StructureDefinition/Patient
After:https://yourserver.com/fhir/StructureDefinition/Patient?_elements=snapshot.element.min,snapshot.element.max,contact&_elements:exclude=*.meta
When to use Graph QL vs _Elements
If you need as close of a representation of your data as possible, use _elements. It maintains the structure based on the constraints you put on it. It’s also useful when you’re working with resources that are large in file size. Let's say you're working with DocumentReference resources, which contain gigabytes of image files. First make a call for the DocumentReference in question, while making use of the _elements parameter in your call.
That would look something like this:https://yourserver.com/fhir/DocumentReference?_elements:exclude=*.content.attachment.data
Once you get all the details about the resource, and the user wants to download their file, make a separate call with the unmodified version resource (in other words, excluding the _elements parameter), since the user has explicitly asked for the file this time. This helps cut down on the initial load time for a resource, and only call for larger data when necessary.
GraphQL implementations provide intelliSense about a FHIR object structure. This means any application that can handle GraphQL queries can determine how a FHIR object is structured—without a need for third party libraries defining the object structure ahead of time. GraphQL is also helpful when you’re less concerned about the FHIR object structure and more concerned about getting a representation of a resource that contains a specific element you’re looking for.
One example of how to do this is when you need to find one specific answer in a QuestionnaireResponse, and that answer is a reference to another resource. You might also know the linkId of the answer you need. In that case, you could write the query like this: https://yourserver.com/fhir/QuestionnaireResponse/123/$graphql?query={item(linkId:2){linkId,answer@flatten{valueReference@flatten{reference}}}}
Where $graphql?query={item(linkId:2){linkId,answer@flatten{valueReference@flatten{reference}}}}
gives us a flatten version of a resource with only the specific element that we’re requesting (the element being an answer with linkId of 2). The end result would be the following:{
"data": {
"item": [
{
"linkId": "2",
"reference": "DocumentReference/15781"
}
]
}
This is definitely JSON, but not the typical FHIR object structure you’d normally get. Because GraphQL support is still a work in progress, you should review the capabilities of a FHIR server's offerings first to see how to best take advantage of GraphQL in your application.
It's also important to note that GraphQL is only helpful for querying data, not writing data to the server.
Pro Tips
- Don't use object keys when working with search parameters. Look in the FHIR Spec and pay attention to the provided search parameters.
- Use "count search parameter" instead of "total" if you need a count of all your resources.
- Aggregate data with _include, _revinclude & iterate (or recurse for STU3)
- Use the FHIR TS Library to integrate defined FHIR interfaces & classes for your JavaScript & TypeScript applications
- Use the SWISS testing tool to be sure you've configured your endpoints correctly
- Need to add OAuth 2.0 & OIDC to your application? Look no further than SMART IT’s SMART JS Client Library
SMART on FHIR Improves Interoperability, Reduces Friction and Increases App Use
The healthcare app you're standing up has the potential to improve or even save lives. Maybe it gives patients more control over their health. Or maybe it helps provide immediate access to care. Either way, it’ll only save lives if it's used. Your app's potential won't be realized if it stalls in development, if it isn't found or downloaded, or if the user experience is laggy and error-prone.
Take advantage of SMART on FHIR to save time on app development and maintenance. Gather data in an efficient way to improve your end-user experience. And, if you do those things correctly, you can promote your app on the SMART Apps Gallery where it will be seen, downloaded and used by thousands.
Thanks to FHIR, you can aggregate data in one API call, avoiding multiple calls and saving time and money. Smile’s FHIR-based CDR has a built-in OAuth2/OpenID Connect server that makes it easy to leverage the diverse ecosystem of SMART applications. That's what we call working SMARTer without working harder.
Ready to break free from legacy applications and siloed data stores? We're here for you. And we're excited to see what you build.