Skip to main content

· 23 min read
Eric Morgan

Code reviews are an important part of software development. By reviewing each other's code, teams can catch bugs early and maintain consistent code standards across a project. Beyond the technical benefits, code reviews foster collaboration, knowledge sharing, and help identify potential improvements that might be missed in isolation. When done effectively, code reviews not only improve the quality of the codebase, but also enhance team cohesion and individual growth. Code reviews help the developer, the reviewer, and the team as a whole.

Why do we need code review?

Code review plays an essential role in providing an additional layer of scrutiny to the development process. Reviewers help to ensure that all decisions made by the developer are sound, consistent, and aligned with the project's goals. This process not only catches potential errors, but also fosters better practices, offering a broader perspective that strengthens the overall quality of the code.

Learning

Code review is one of the most effective ways to create experts within a development team. By reviewing others’ code, developers gain exposure to different approaches, best practices, and problem-solving techniques that they might not have encountered on their own. Conversely, having one’s own code reviewed provides valuable feedback that challenges assumptions, improves decision-making, and reinforces good habits.

This exchange of knowledge accelerates growth, turning junior developers into seasoned engineers and keeping experienced developers sharp. Over time, this continuous cycle of learning and refinement raises the overall skill level of the team, ensuring that expertise is shared rather than siloed. Code review isn’t just about catching mistakes—it’s a mechanism for building mastery.

Code review is also one of the easiest ways for new developers to become acclimated with a code base. Unlike simply reading through large amounts of unfamiliar code, reviewing an actual change forces developers to trace through the logic in the context of a real problem. This makes the learning process far more engaging and practical, helping new team members understand how different components interact, what patterns the team follows, and where potential pitfalls lie. Over time, this hands-on exposure builds confidence and fluency with the code base much faster than passive observation ever could.

Accountability

One of the key roles of a code reviewer is to hold the developer accountable for meeting project requirements and objectives. While developers are responsible for writing the code, the reviewer ensures that the implementation aligns with the intended goals, functionality, and scope. By carefully examining the code, the reviewer confirms that the developer’s work is on track and that nothing is overlooked, providing an added layer of responsibility to the development process.

Even in a team full of dilligent developers, accountability is very important because often the developer has perverse incentives not to adequately complete the task. For example, the developer may be under pressure to close a particular ticket and in an effort to close it quickly, may miss some requirements.

Safety Net

You can think of a code review as the editing phase of a writer's process. When writing an article, having a second set of eyes ensures clarity, accuracy, and a more polished final product. More importantly, it provides a validation that the content makes sense to readers beyond the author. Similarly, code reviews allow developers to refine their work, ensuring that the final code is not only functional, but also clean, readable, and maintainable. Just as no writer would publish a piece without proofreading, no developer should push code without ensuring that it is as clear and understandable as possible for the entire team.

Code reviews can also be compared to roles where one person acts as a backup to catch potential mistakes. For example, a spotter in weightlifting is there to provide assistance and ensure safety in case the lifter struggles with the weight, just as a code reviewer helps identify issues that might have been missed. The copilot in an airplane supports the pilot by double-checking navigation and procedures, much like a code reviewer ensures that the developer’s approach is sound and the code is free of errors. Lastly, a safety net in acrobatics catches the performer if they fall, much like a code review catches overlooked mistakes before they impact the final product. In all of these cases, the secondary party acts as a safeguard, helping to ensure everything runs smoothly and safely.

People Involved in a Code Review

There are several actors who may be involved in a code review, which will be described below.

Developer

The developer, sometimes referred to as the author, is the person who wrote the code that is under review.

Reviewer(s)

The reviewer (of which there can be more than one), is the person who is tasked with reviewing and testing the developer's code.

Tester(s)

The tester is the person who is tasked with testing the code change. Sometimes a tester is not included as part of the process, and testing responsiblities are limited to the reviewer.

Code Review Tools

The most common, and classic, tool for code reviews is Github, which provides a structured workflow for proposing, reviewing, and merging changes through pull requests (PRs). Although many other tools exist, this article will focus on Github tooling due to its prevalence.

Preparing a Code Review (as Developer)

Once a code change has been completed, the developer's next responsibility is to prepare the code review. This preparation is important for setting the code reviewers up for success. The developer should strive to provide as much information as necessary for a code reviewer to understand the change and be able to complete a quality code review.

Some elements to include in a good code review may include:

Description of the change

Don't leave it ot the code reviewer to try to decipher what is being changed and why. Provide some background information. Tell the code reviewer why you are making this change. And also tell the code reviewer how you are making this change.

Example: Fixing a bug in a date formatter

This change fixes an issue where dates in the report export feature were displayed in UTC instead of the user's local timezone. The problem was caused by the backend returning dates without timezone information, which the frontend interpreted as UTC. To fix this, I updated the backend to include timezone offsets when sending date values and adjusted the frontend to correctly parse and display them in the user's local timezone.

The description is important because it provides useful context to the reviewer for when they start the code review. They have a general idea of what they're looking at and what to test for.

A description is also important for whenever someone goes back to perform a Root Cause Analysis (RCA). Perhaps years into the future, developers will look at this code and wonder why it was written a certain way. Perhaps they'd like to change it to fulfill new requirements, but are worried about breaking some obscure workflow that they are unfamiliar with. Those developers can go back and find the Pull Request (PR) that originally introduced the code. If there is sufficient context and information, they should be able to answer any questions that they have.

Screenshots

If there is a visual element to the change, include a screenshot or a video. A picture is worth a thousand words, and sometimes a quick screenshot is all that's needed to quickly align the code reviewer on what they should expect to see when they test the code.

Test Instructions

Tell the code reviewer how to actually test the code. Don't assume that the code reviewer is as intimately familiar with this workflow as you, the developer, are. The code reviewer may not have ever encountered this workflow before. The code reviewer could even be new to the team. Providing steps for how to actually test the change makes the code review faster and more efficient by not forcing the code reviewer to spend time figuring out how to test it.

Tips:

  • Inlcude any settings, configuration, or other build that might be necessary to test the change.
  • Include any sample data that might be helpful in testing the change

Why Else is Documentation Important?

Documenting a code review well is not just important for ensuring the code reviewer is best equipped to perform a quality code review. But this documentation also serves as valuable information in the event that a PR is ever revisited at some point in the future. Sometimes, bugs are discovered months or years after they have been created. Serious issues may require some investigation into what happened. Being able to review a PR's documentation provides hints and important context surrounding the decisions made at that time. The same is also true for when a piece of functionality is considering being changed. Being able to review the context around the original functionality can inform everyone on whether or not the proposed change would reintroduce any issues that had already been discovered.

Starting a Code Review (as a reviewer)

Code review is a back-and-forth process in which a code reviewer will point out potential issues and ask questions, and then pass it back to the developer who will address or reply to all of those comments and pass it back to the reviewer. The process will then repeat until both parties are satisfied with the quality of the code change.

As a reviewer, there are typically at least two phases to every code review.

  1. Static code review
  2. Testing

Phase 1: Static Code Review

The first thing a reviewer should do is review the diff of the code changes. This will help the reviewer to understand the modules that are being modified as part of this change and will provide ideas about what to eventually test.

It's even ok for the reviewer to write out a few notes as they are reading through the diff. For example, a change in one file may look suspicious, so perhaps the reviewer wants to make a note about that to come back to it after they have finished reviewing all of the files.

During the static code review phase, the reviewer can point out potential coding style issues, such as code that doesn't meet the style guidelines of the team. There may be more serious code-related issues, such as branches of code that are not being covered or handled properly.

NOTE

Nitpicking should be kept to a minimum as it takes up time and diminishes the autonomy of the developer. However, from the developer's perspective, please keep in mind that although you are the developer of this code, ultimately the code is "owned" by the team; so be open to suggestions that might be more aligned with the rest of the team.

After the initial static code review has completed, pass the PR back to the developer to have them respond to or address all open issues.

TIP

If you are using Github for your code review, use the "Start Review" button to make all of your comments. What this will do is prevent the developer from being notified about every individual comment. This eliminates the inefficiency of the developer not being sure whether you have actually finished the code review or not and avoids chaos of a developer making code changes while a reviewer is still actively reviewing them.

Additionally, as a reviewer, you may realize that some of your early comments are no longer applicable after you have read through more of the code. In that case, you may wish to go back and delete some of those comments. By using "Start Review", you allow yourself the ability to write or remove comments, uninterrupted, until you have fully completed your review.

Phase 2: Functional Testing

The reason for the two phase approach in Code Review is because if any changes are made to the code as a result of Phase 1, then any testing that was performed is now invalidated. When code changes, the state of the entire system changes. Any testing that was done on a previous state would need to be redone on a new state to ensure that there were no regressions. So, it's a waste of time to being testing if there is a possibility that the developer will make further changes.

Once the developer passes the PR back to you after making any necessary Phase 1 changes, now you can begin functional testing. Put yourself into the shoes of a user and test the functionality of the change. Try to think of variations of the workflow that might cause the code change to break. Use the added knowledge you gained from reviewing the code itself to identify areas where you might be able to break it. For example, maybe you found a function that isn't adqueately guarding for null or invalid inputs. Try to see if you can actually get those invalid inputs into that function. If you can, write up an issue and explain to the developer what happens when you perform that workflow.

Document all functional issues that you find. When you find these issues, it's best to write them up in the same way that you would write a bug ticket. In other words, include the steps to reproduce the issue, what your expected behavior is, and screenshots to help clarify the issue.

In the same way that you want the developer to make it easy for you to test their code, you should make it easy for the developer to understand and fix the issue that you have documented. If you don't include the "expected behavior," for example, then sometimes an issue can be misinterpreted and won't be addressed in the way that you had in mind.

TIP

As a code reviewer and tester, it is your job to identify deficiencies in the new code. If there are any issues, you should consider it your mission to find them. In fact, it's your responsibility. Do whatever you can to find those issues because it's better that you find them, than an end-user does.

Pull Down the Branch

It is absolutely essential that you pull down the developer's branch when doing code review. Reviewing the diff of the PR is not sufficient.

First, if you don't pull down the branch, then you may not be able to actually test the code. You may not be able to verify that the code actually compiles and passes all of the linting and other checks it needs to pass. You may not be able to run the unit tests and verify they all pass.

Many teams now have automated checks for linting and unit tests that will prevent a PR from advancing if they do not pass. Some teams may have the capability to test a branch of code without actually pulling and running the code. If so, great! Utilize those tools as they save a lot of time and add a lot of wonderful safeguards. However, even with these tools, you will still have to download the code...

Because, suppose a developer deletes a global constant. Or suppose they rename a function. Any of these actions would require all references to that constants/function/etc. to be updated. Hopefully the developer has done a quality code search and found all the places it was referenced. But it's your job as the reviewer to double-check it! And please note that not all programming languages are strict about catching these kinds of issues during the build process. While some modern IDEs or linters may flag missing or renamed references, they might not catch every instance, especially if the code is dynamically generated or relies on less obvious patterns.

Please Test the Code!

As one final reminder: Please test the code you are reviewing!

Ensuring that the code actually works is the absolute most important function of a code review.

Testing code is extremely important, yet it requires more effort than simply pointing out readability issues like naming conventions or formatting. Running the code, testing various scenarios, and ensuring correcntess demands time, attention, and sometimes more advanced setup. Unfortunately, many reviewers either don't have the time or don't want to invest the necessary effort, so they default to giving feedback on subjective elements like style, which are much easier to critique.

Readability, while important, is highly subjective. Unlike functional correctness, which requires understanding the underlying business logic and testing how the code performs in real scenarios, readability is often opinion-based. Because of this, many reviewers feel comfortable offering opinions on these items without needing to deeply engage with the core functionality of the code. It is a low-barrier way to contribute to the review, but often at the cost of missing more critical issues. This tendency to focus on the "easier" comments can lead to a less effective review process, where the focus shifts from verifying correctness to nitpicking style. It's also a wasted opportunity to catch bugs in the system before they make it into production.

Reviewing is Not a Rubber Stamp

Code review should never be treated as a mere formality or a rubber stamp to approve changes. It’s a crucial part of ensuring the quality, functionality, and maintainability of the codebase. A reviewer’s role is to actively engage with the code, ask questions, and challenge decisions where necessary. Simply glancing over a pull request and clicking "approve" without thoroughly testing the code, understanding its impact, or considering the broader context of the project undermines the purpose of the review process.

In some cases, developers may feel pressured to quickly approve code to meet productivity metrics or gaming the statistics, like review completion times or the number of pull requests approved. This kind of "rubber-stamping" creates a false sense of progress and can lead to overlooked errors, poor-quality code, and missed opportunities for improvement. Effective code reviews should focus on quality, not quantity, and reviewing with genuine attention to detail ensures that the team produces high-quality, maintainable software in the long run.

Responding to Code Reviews (as a developer)

As the developer, it is your responsibility to address or respond to all issues written up by your reviewers. Some of those issues may not be legitimate issues. For example, they may be expected behavior. That's ok, simply reply to the issue and say so.

If a reviewer writes up a legitimate issue that requires a code change, make the code change and respond back to the issue indicating that you have fixed it and adding any additional context that may be useful about that issue. For example, "I fixed the issue by doing ...".

It's often useful to write a separate commit for every individual issue, particularly when there are a lot of issues or a lot of reviewers. For example, if 100 issues are written up about a particularly large PR, and they are all fixed in one giant commit, it can be very difficult for reviewers to go in and verify each of the individual fixes. On the other hand, having individual commits for each individual fix makes it easy to link the reviewer to the exact change made to address a specific issue.

TIP

Here are a few ways in which you might respond to a code review comment that has been addressed:

  • 👍
  • Fixed
  • I have addressed this issue by changing...
  • This is a good suggestion, but I don't think we should do it because...
  • This is out of scope of this PR, so I have created a ticket for it: {ticket}

Reviewing Patches to a Code Review (as a reviewer)

As you write issues to the developer, you must also verify that the fix is made in a satisfactory manner. If you have written up an issue and the developer indicates that they have fixed it, you must follow-up and ensure the developer has actually and adequately fixed the issue.

There are times when there is a miscommunication and the developer may not have understood the issue that was written up by the reviewer. In that case, the developer may have fixed the wrong thing.

That's why it is up to the original reviewer who wrote up the issue to verify that the fix was made.

This process may also utilize the Two Phase approach. First, the reviewer can review the diffs of the intermediate commits that were made to address the issue. And if those look good, then the reviewer can move on to testing and verifying that the functionality itself was fixed.

Resolving Comments

It is the reviewer's responsibility to resolve all of the comments that they write up. Resolving the comment closes the loop on an issue. That loop being: the reviewer reports the issue, the developer fixes the issue, and the reviewer verifies the fix. Resolving the comment cleans up the PR and makes it easier to visualize how many outstanding issues still remain.

Ideally, a code review should not advance if there are any unresolved comments on it. Because if so, that means that a reviewer has identified a problem with the code and we should not proceed until that problem has been addressed. Even if the problem is not "fixed," perhaps it should be written up as a ticket to address in the future.

Writing Up Separate Tickets

Sometimes, throughout the process of testing a PR, a reviewer will find issues that are unrelated to the PR they are reviewing. Or sometimes, they may believe that an issue is related to the PR, but in fact, it is out of scope.

It's perfectly ok to write these issues up in the PR. The expectation is that the developer will investigate them and respond back to them stating that they are unrelated or out of scope.

However, out of scope or not, these are still issues that may need to be fixed. Don't let them get lost inside the PR. Instead, convert them into tickets that can be reviewed and prioritized independent of the PR. That helps to ensure that the application as a whole is being maintained with high quality.

If you are reviewing a PR and you find an issue that you know is unrelated, please don't just let it go. Eventually someone will find the issue and that someone may be a user. If you notice something is wrong, make sure to address it before it gets to that point.

Types of Issues

There are a variety of issue types that might come up during a code review. Below I have made a list of some possible categories of issues:

  1. Crash
  2. Functional Problem
  3. Performance
  4. Security
  5. Visual Glitch
  6. Code Style
  7. I18N
  8. Question

FAQ

How do I handle nitpick comments?

As a developer: As a developer, nitpick comments can sometimes be frustrating. It's tempting to view these as damaging your independence and autonomy as a developer, for no legitimate functional reason. But try to consider that once the code is merged, it is no longer "my" code, but rather it's "our" code. And while a segment of code may make perfect sense to me, it may not be as clear to another reader. My code reviewer is one of those readers, and if they are having trouble understanding it, then maybe I haven't written the most clear code.

That doesn't mean to give in to every coding style issue that gets written. It's best to choose your battles. If it's important to you, say so. If you don't care so much, consider changing it, because it was important enough to the reviewer to write it up.

As a reviewer: As a reviewer, try to avoid nitpick comments unless they are significant enough to comment on. It helps to have a reason for the change beyond just personal preference. Recognize that comments like this are ultimately subjective and that you and the developer may not always agree on that subjectivity. Nitpicking comments can add overhead to code reviews that take up time, so we should be sure that those time investments are truly worth it.

Often, nitpicking comments are hypothetical in nature—suggesting changes that may not have a concrete impact on the application at the current time. Before making such a comment, ask yourself: Does this change meaningfully improve the code, or is it just my personal preference? If the answer leans toward preference, consider whether it's worth mentioning. When in doubt, framing suggestions as optional and explaining their rationale can help keep the discussion productive. The goal is to foster collaboration, not to enforce personal coding styles at the expense of efficiency and morale.

I often challenge myself to find an actual functional problem with the code before writing up comments like this. For example, a reviewer might suggest replacing a == comparison with === in a conditional statement. At first glance, this might seem like a minor nitpick. However, if the comparison involves values of different types (say, strings and numbers), then the == could cause unexpected behavior. If you can find an actual bug and your comment provides a tangible benefit, then we can ensure that feedback is both constructive and worth the developer's time to consider.

Lastly, if you are going to leave a nitpick comment, please make sure that it actually works. How many times has someone told you to change the code to something and then you try it and the syntax is illegal? For example:

Change it to this MyData. (But actually, it has to be MyData<T>)

Is Paired Programming A Substitute for Code Review?

Paired programming is an excellent tool for writing code and having it reviewed simultaneously. It's also beneficial for developers to observe how other developers work. Often times, you can pick up useful tips that you wouldn't have otherwise known about. For example, you might learn some new and better ways to debug code by watching how another developer does it.

Paired programming can be considered a "code review" in that it's done in parallel to the development itself. After a paired programming session, there's nothing wrong with approving a PR as soon as it's done, because the code has indeed been reviewed by the person doing the paired programming.

In some situations, paired programming can save a large amount of time. Typically, when a PR is passed to a reviewer, the reviewer needs to spend some time understanding how the code works and what the change is doing. The developer has already invested the time to do this while they were doing their development. By pair programming, we avoid doing some of this twice.


Testing Code Requires More Effort Than Pointing Out Readability Issues Running the code, testing different scenarios, and verifying correctness takes extra time and effort. Many reviewers either don’t have the time or don’t want to put in the effort, so they default to commenting on naming, formatting, and structure.

Readability is Subjective, So Everyone Feels Qualified to Give an Opinion

  • Unlike functional correctness, which requires understanding the business logic and testing the changes, readability is opinion-based and easy to critique.
  • People naturally want to contribute something to a review, and readability is a low-barrier way to do so.

· 3 min read
Eric Morgan
  • Bulk Export is a way to download large amounts of data from a FHIR Server
  • There are different types of Bulk Export, depending on the kind of data you need
  • Not all EHRs implement Bulk Export the same way, but Plasma can help with the cross-platform compatibility

FHIR Bulk Export is a way to download massive amounts of data from a FHIR server that you can then use to initialize a database, perform an analysis, or train an AI model. In this article, we will give further details about Bulk Export, and demonstrate how it can be achieved.

Specifications

You can read more about the FHIR Bulk Export specification here: https://build.fhir.org/ig/HL7/bulk-data/export.html

There are three types of Bulk Export:

  1. Patient - Exports all data for a particular patient
  2. Group - Exports all data for a group of patients
  3. System - Exports all data in the entire system

Bulk Exports are asynchronous operations that could take multiple seconds, minutes, or even hours to complete. Once a Bulk Export is initialized, you can check the status of the Bulk Export periodically via a URL you will receive after kicking off the Bulk Export.

The status of the Bulk Export will usually provide a percentage complete as well as a number of seconds that you should wait before checking the progress again. It is a good idea to abide by the delay period, or else you may get blocked from checking on the progress.

Once the Bulk Export has completed, you will receive a list of files in ndjson format containining all of the data, which you can then download.

Demonstration

I have built a demo app to demonstrate the bulk export feature here: https://bulk-export.smart-on-fhir.com.

Plasma provides a convenient interface through which you can work with Bulk Exports. When calling the Bulk Export function via the Plasma SDK, you should provide callback functions that will be called during certain events throughout the lifecycle of the Bulk Export

  • onKickoff - Will be called once when the Bulk Export is initialized
  • onStatusChecked - Will be called each time the status is checked. Plasma will adhere to the recommended delay time given by the FHIR server
  • onComplete - Will be called once when the Bulk Export is completed
  • onError - Will be called once, if there is an error with the Bulk Export

Here is a code sample of how this would look, if you are just printing out information to the screen:

const onKickoff = async (kickoffResponse: IBulkExportTypes.IStartBulkRequestResponse) => {
console.log(`Bulk export has started. Status is available at: ${kickoffResponse.contentLocation}`);
};

const onStatusChecked = async (status: IBulkExportTypes.IGetBulkRequestStatusResponse) => {
if (status.bulkStatus === IBulkExportTypes.BulkExportStatus.Completed) { return; }
console.log(`Bulk export status is ${FHIRBulkUtils.getBulkExportStatusDisplay(status.bulkStatus)}`);
console.log(`Progress message: ${status.xProgress}`);
console.log(`Retry-After: ${status.retryAfter}`);
};

const onComplete = async (status: IBulkExportTypes.IGetBulkRequestStatusResponse) => {
console.log(`Bulk export is complete!`);)
};

const onError = (err: any) => {
console.log("Error on Bulk Export: ", err);
};

await plasma.doBulkGroupExport(
groupId, exportParams,
onKickoff, onStatusChecked, onComplete, onError
);

Please be aware that not all EHRs implement Bulk Export the same way. Using tools such as Plasma can help to achieve cross-platform compatibility with little effort.

Conclusion

Bulk Export can be a great asset if you have a need to download large amounts of data at once, as mentioned in the use-cases listed above. Plasma can help you accomplish this very easily.

For a video demonstration of this concept, please check it out here: https://www.youtube.com/watch?v=E8FTsiCEQhY

· 3 min read
Eric Morgan
  • SMART-on-FHIR scopes can be specified in different formats
  • The format depends on the EHR and FHIR Server being connected to
  • Plasma can format scopes in whichever way is necessary

When building a SMART-on-FHIR application, requesting scopes is yet another small, but crucial detail that may differ from EHR-to-EHR. When launching your app, you must pass a string representing the scopes that your app is requesting and it must be formatted precisely based on the EHR or FHIR Server you are connecting to.

Usually, when registering an app with an EHR, you are asked which scopes your app will be requesting. Here are a few examples of how some of the larger EHRs ask you which scopes your app is requesting

Epic Cerner Athena

However, after registering your apps, you must make sure to request those scopes using the proper format for that EHR.

Scope Format

The format of FHIR scopes can be found on the website.

There are three basic patterns for scopes.

  1. Resource Scopes Resource scopes have the pattern context/resource.actions. For example, patient/Observation.rs means an app can read and search for all observations about a patient.
  2. Launch Scopes Launch scopes have the pattern launch/context and indicate that the app will need some piece of information before launching. For example, launch/patient indicates that an app requires a patient context.
  3. Other Scopes Other scopes are simply keywords that specify some information or functionality that is needed. For example, fhirUser specifies that the current user should be available as a FHIR resource.

Scope Versions

There are also two types of versions for specifying scopes, called v1 and v2. v2 is the current version, but sometimes v1 scopes still need to be supported. You can read about the differences between v1 and v2 scopes at the link above.

When launching your app, you must be sure to pass scopes the way that the server you are connecting to expects them to be formatted.

Plasma Solution

With Plasma, we have defined a discrete way to represent scopes and we provide parsers and formatters that can generate the scope string. The formatters are configurable, and as always, we provide plugins that generate the correct format for all of the major EHRs.

Scope Parser/Formatter

Our scope module works like the code shown below. Here, we are parsing a string of scopes in the v2 format and then converting them back to a string in the v1 format.

// Parse the string into discrete scopes...
const fhirScopes = FHIRScopes.fromString("patient/Patient.rs launch launch/patient fhirUser");

// Define a formatter for how you want to format your scopes...
const formatter = new FHIRScopeFormatter({ version: "v1", expandResourceScopes: false });
// Or: const formatter = FHIRScopeFormatter.forEpic();
// Or: const formatter = FHIRScopeFormatter.forCerner();

// Format the discrete scopes as a string in the intended format...
const sScope = formatter.formatScopes(fhirScopes);

Scope Builder

We also provide a UI tool that lets you build a scope string and set whichever format you need.

v2 v1

Scope Management

In addition to formatting scopes properly, some EHRs require certain custom scopes to always be included in every launch.

Use Plasma to manage your SMART-on-FHIR applications to ensure that your app is always conforming to the latest requirements of your target platforms.

· 2 min read
Eric Morgan
  • The Plasma platform makes integrating with EHRs simple and easy
  • Applications can connect to EHRs with no changes to code
  • Our tooling can expedite the build and development process

What Is Plasma?

Plasma is a platform and set of tools that make integrating with an EHR as easy and seamless as possible.

Not in the Health IT industry

Plasma Platform

Plasma SDK

The Plasma SDK is a collection of tools and libraries to help facilitate the building of an integrated Health IT application

Formatters

FHIR resources can be cumbersome to work with, especially when there are multiple properties that can potentially hold the data we need or when there are multiple nested layers of properties, all of which can be null. Our API library can handle this, grabbing the data in a presentable format for you and keeping your codebase nice and clean.

UI Components

UI Component

We provide a library of fully cuztomizable UI components that bind directly to FHIR resources and can greatly improve the efficiency of building a SMART-on-FHIR application.

Rather than going through the tediousness of building components to display a FHIR resource, just use our components and style them however you'd like.

Unit Conversion

Often, data will be returned to applications in units of measurement that are not desirable (for example, we may not want to show the patient their height in centimeters). Our tools can quickly and conveniently convert units of measurement to whichever unit is needed.

De-Duplication and Merge

Merge

When data is pulled from multiple health systems, there are likely to be duplicates that are just slightly different.

Use our tools to identify these duplicates and merge them together to ensure data is neither lost nor duplicated.

· 5 min read
Eric Morgan
  • SSO with SMART-on-FHIR can prevent your users from logging in twice: Once for the EHR, and once for your application
  • This article discusses strategies for implementing this into your application
  • Plasma provides a mechanism to make this process quick and easy

Multiple Logins

Many people building SMART-on-FHIR applications have an existing app with an existing user management system. In these applications, users (typically clinicians) must first login in order to use the app.

When converting an app like this to SMART-on-FHIR, the user will also be required to login to the EHR in order to have access to the EHR's clinical data. Requiring users to login twice to use your app can be a source of frustration and increase the number of clicks it takes for your users to accomplish their goals.

The ideal solution is to have your users login a single time, through the EHR, in order to both use your application and interact with the EHR.

SMART-on-FHIR Standards

SMART-on-FHIR provides a way to receive an id_token which can be used to gather identity data about the currently logged in user. This data can then be incorporated into your own user management system to link existing accounts, or to initialize new user accounts.

This will work for both a Clinician-facing and a Patient-facing application. The user information will come back in the form of a FHIR resource, whichever is appropriate to the situation (a Patient resource or a Practitioner resource).

You can read more about the SMART-on-FHIR standards at the following URL: https://build.fhir.org/ig/HL7/smart-app-launch/scopes-and-launch-context.html

Case Study 1: Brand New App

In this case study, you are still building an application and you do not have any existing users. This is the simplest case because you will not have any legacy data to contend with.

One option you have in this case is to use the existing user management system of the EHR and just copy and customize the data necessary for your application.

We recommend placing a login button for whichever EHRs your application needs to support and direct the user to the authentication page for that specific EHR. The image below shows a sample implementation for an app that can support multiple EHRs. You may choose to provide individual buttons for quick access to the relevant EHR, or provide a drop-down list for the user to choose from.

SSO Demo

Once the user has logged in to their EHR, you can retrieve the identity information and populate your user management system accordingly. The user is now authenticated and can be granted access to your application's features.

Case Study 2: Existing App

In this case study, suppose you have an existing clinician-facing application with many users who are accustomed to logging into your app using their email and password.

We want to link each user's account to their EHR account and have them login via the EHR rather than with their email and password.

We recommend having the user keep their existing login and then "linking" their account to the EHR account. You can provide a "Link Acocunt" button that will direct the user to the EHR's login page. Once the user has logged in, you can retrieve identity information and store that linked information into your user management system.

Depending on your implementation, you may want to save refresh tokens so that you can refresh the user's access periodically and avoid frequent logins.

Case Study 3: Multiple EHR Accounts

In this case study, suppose you have a patient-facing app and suppose that some of your users (patients) have accounts registered with multiple EHRs. This can be quite common if the patient receives care from two different health systems in their community. Another example may be that the patient has changed insurance providers and was required to change health providers.

In this situation, we recommend maintaining a one-to-many relationship between your users and their health system connections. You can still retrieve identity information for each health system connection and you can decide in what capacity you should use it. We recommend using the patient's current health system data when pulling any kind of demongraphic identity data.

SSO with Plasma

With Plasma, we provide a seamless integration to an EHR's SMART-on-FHIR authentication service. Our platform can get your application and users connected to an EHR with the push of a button.

We provide an endpoint for you for every EHR system that you need to support. You can direct your users to this endpoint to have them login and connect to the EHR.

Once your users have connected to the EHR, we provide a convenient endpoint that will automatically retrieve the logged-in user's information. This can be done in a single step by calling the .../plamsa/sof/whoami?readFhirUser=1 endpoint.

A sample response from this API can be seen below.

SSO Data

Demonstration

To demonstrate this functionality, we have built a very basic app using the Plasma Platform. This app connects to both the Epic and the SMART Health IT sandbox environments. Feel free to try it out here: https://sso.smart-on-fhir.com/

Get Started

Check out https://medblocks.org/plasma to get started today!

· One min read
Eric Morgan

When working with a new FHIR server, there are certain characteristics about the server that may be helpful to know.

For example:

  • What operations are allowed on each resource?
  • What search options are available for each resource?
  • What extensions are defined on this server?

Metadata

This data is all available in the FHIR server's /metadata endpoint.

For example, take a look at the SMART Health IT server's /metadata: https://launch.smarthealthit.org/v/r4/fhir/metadata

Let's examine some of the properties of the AllergyIntolerance resource. To do this, search for "type": "AllergyIntolerance".

We can see that it allows for several searchParams, including date, severity, and code.

And it defines one extension: http://hl7api.sourceforge.net/hapi-fhir/res/extdefs.html#resourceCount

Plasma Dev Portal

The Plasma Dev Portal makes it extremely easy to access this information. We provide an intuitive UI for easily visualizing the details of a given FHIR server.

Just navigate to the [https://toolkit.plasmahealth.net/toolkit/fhir-server-explorer][FHIR Server Explorer] and paste the FHIR server that you're interested in. Data about that server will be gathered and presented to you.

FHIR Server Explorer

· 2 min read
Eric Morgan

For Patient-Facing apps, users (patients) are first asked for consent before the application is given access to their personal health data. How should your app handle the case when a user declines permission to a particular resource?

Fortunately, this is a common pattern that we have all encountered at one point on our smart phones.

Permission to Use Camera}

And thus, we can duplicate the patterns that these apps use to handle cases when a user declines access.

Always Have a Fallback

Never assume that every user will grant your app access to the resources you are requesting. Your app must provide a fallback in the event that consent is not granted.

If this resource is crucial to the functioning of the application, then it is acceptable to leave a warning message telling the user that they cannot proceed if they do not provide access to that resource.

For example, an app that displays a patient's allergies and associated information, must have access to a patient's AllergyIntolerance or else the app will not be able to perform its designated function.

On the other hand, if the resource is only supplementary to the application, but the application can still function without it, consider just hiding that feature and carrying on.

Testing is a Must

The most important thing to do is to make sure you test this case. Don't always test in the "best case" scenario. Make sure your testing also involves checking what happens when a user denies permission to certain resources.

· 6 min read
Eric Morgan

When working with FHIR data from within an application, we must decide on how to write updates back to the FHIR server. Generally, there are two strategies for doing this:

  1. Inline Updates: Everytime a change is made, immediately save that change back to the server
  2. Bulk Updates: Track changes and save them all in bulk based on a certain event, such as a user pressing Save, or the application being closed

Inline Updates

Inline updates mean that any time a change is made, that change is immediately saved back to the server. In this case, the application is nearly always synchronized with the server and limits some risks, such as the application crashing and all changes being lost, or the data becoming stale.

This method also tends to be simpler as it doesn't require any additional code for tracking changes.

Example Code (with Plasma)

Take, for example, an application that allows users to manipulate a patient's allergies. Any time an action is performed (user adds a new allergy, user changes something about an allergy, etc.), the new data will be saved back to the FHIR server. Below is some sample Plasma code that may work for this case.

async function refreshData() {
this.data = await this.plasma.readAllergyIntolerance(patientId);
}

async function createAllergy(allergy: r4.AllergyIntolerance) {
await this.plasma.createResource<r4.AllergyIntolerance>(allergy);
await this.refreshData();
}

async function deleteAllergy(allergy: r4.AllergyIntolerance) {
await this.plasma.deleteResource(allergy);
await this.refreshData();
}

async function updateAllergy(allergy: r4.AllergyIntolerance) {
await this.plasma.updateResource(allergy);
await this.refreshData();
}

As you can see, the code is very simple. We have a function, refreshData that loads data directly from the FHIR server and assigns it to this.data. Any time any CRUD operations are performed, we simply call the appropriate function and then refresh the data back from the server.

Pros/Cons

Pros:

  • Simple to implement
  • Protect against unexpected crashes

Cons:

  • User workflows are slower due to waiting for loading
  • Server updates may not appear immediately

The second item in the Cons list, server updates may not appear immediately, is a major problem with this approach. Very often, you may submit a change to a FHIR server, and that change may not appear for several seconds or several minutes.

So, for example, in the code above, we may create an allergy and then call refreshData() to overwrite this.data with whatever is on the server. However, that new allergy may not appear on the first call to refreshData(). This means that the allergy that was just created won't appear to the user and will result in a confusing user experience. Furthermore, it's not clear when the new allergy actually will show up, meaning our application will not know when it should call refreshData().

For this reason alone, we recommend using the Bulk Updates method for writing back FHIR data.

Bulk Updates

Bulk updates mean that changes are tracked locally within the application and then saved all at once after a triggering event. That event could be when the user presses a "Save" button. Or it could be right as the user closes the application.

This method results in a more responsive user experience compared with the Inline approach since there will be no network calls in between actions. However, this approach is more complex as it requires keeping track of some type of changes data structure.

FHIR provides a method to execute numerous actions as a transaction, which is great for this use-case.

One additional complexity with this approach is that there is a greater likelihood that the underlying FHIR data has been modified by another user and there may be data conflicts when attempting to save.

Example Code (with Plasma)

Take again, for example, an application that allows users to manipulate a patient's allergies. In this case, we will track changes made to the data throughout the course of the application, and then save those changes in bulk when the save() function is called. Below is some sample Plasma code that may work for this case.

async function init() {
this.data = await this.plasma.readAllergyIntolerance(patientId);
this.deletedIds: string[] = [];
this.modifiedIds: string[] = [];
this.createdAllergies: r4.AllergyIntolerance[] = [];
}

function createAllergy(allergy: r4.AllergyIntolerance) {
this.createdAllergies.push(allergy);
}

function deleteAllergy(allergy: r4.AllergyIntolerance) {
this.deletedIds.push(allergy.id);
}

function updateAllergy(allergy: r4.AllergyIntolerance) {
this.modifiedIds.push(allergy.id);
}

async function save() {
let bundle = plasma.startBundle();

// Add all newly created allergies...
this.createdAllergies.forEach((allergy) => {
bundle = plasma.addWriteToBundle(bundle, "create", allergy);
});

// Add all deleted allergies...
this.deletedIds.forEach((id) => {
const allergy = this.data.find(x => x.id === id);
bundle = plasma.addWriteToBundle(bundle, "delete", allergy);
});

// Add all modified allergies...
this.modifiedIds.forEach((id) => {
const allergy = this.data.find(x => x.id === id);
bundle = plasma.addWriteToBundle(bundle, "update", allergy);
});

await plasma.executeBundle(bundle);
}

Notes:

  • We track created, updated, and deleted records separately since they have different associated actions for each
  • For updated and deleted records, we only need to track the ID since the IDs are unique. Newly created records do not yet have IDs, so we track the entire object itself
  • We use a Bundle to perform all the actions at once as a transaction

Dealing with Conflicts

If data has been modified by another user in between the time that your application loaded data from the server and the time that your application's user wishes to save the data, you must account for any possible conflicting data.

Similar to how source code is merged, there may be some cases in which conflicts can be resolved programmatically. However, there may be some conflicts which must be resolved by a human being.

This can be a complicated process, but the Plasma Health framework has some guidelines and tools to assist. This will be the subject of a future blog post.

Conclusion

  • To provide the best user experience, we recommend tracking all changes and submitting them at once when ready to save
  • We recommend using a Bundle to submit all changes as an atomic transaction
  • Be cautious about data that may have changed as attempting to save data could result in a data conflict

· 2 min read
Eric Morgan

If every EHR implements their own unique standard, or, even their own unique flavor of a common standard (such as FHIR), it makes intuitive sense to invent a universal standard to consolidate them all, right? Well, there's an old joke about that.

Standards

Should I Create a New Data Model?

You may be tempted to create a new data model or to consider services that create one for you. A universal data model simplifies things. It allows you to use the same code for every platform or situation. It certainly has its benefits, but before investing in it, you should also consider the costs.

When using a service that creates a universal data model, you are locking your app into that company's data model. You're trusting that they will continue to support it, that it will be stable, and that it will have longevity. It will be difficult to eject your app should you decide to take another direction, because your entire application will be dependent on this data model.

Additionally, services that consolidate different data models into one are adding an additional layer between your app and the data. This additional layer adds potential risks and may have some performance impact.

What Should I Do?

If your application has an existing data model, you should map to that. It's going to be extra work for the developers, but with good coding practices, it doesn't have to be painful. Map everything to your data structure and then you own the data. You are free to modify it as you wish. You are not trapped beneath another service's decisions.

FHIR Standard

At Plasma Health, we stick to the FHIR standards and supply a utility framework to make working with it easier. Is FHIR a perfect data model? No. Does FHIR exist? Yes.

We strive to make interacting with EHRs as easy as possible and we want your app to be able to develop and innovate quickly. Our framework facilitates that, but at the end of the day, it's still using FHIR. Support for FHIR across EHRs is growing rapidly and Plasma Health can help bridge the gap between an EHR's FHIR service and your application. And if you ever decide to eject from the Plasma Framework, your app was still written to the FHIR specification rather than a proprietary data model.