Should you use FHIR in your frontend apps?

Anais Limpalaer
Lifen.Engineering
Published in
8 min readNov 19, 2021

--

This is the story of how the frontend team at Lifen has dealt with FHIR as the main data structure for its client-facing applications. But the learnings in this article can apply to any complex, storage-oriented data structure.

Being FHIR Native

At Lifen, we are committed to being “FHIR Native”, and we have been from the beginning. You can read all about it in this article, written by

, our CTO, in early 2019.

Back in 2017, we started to build our main frontend app around FHIR Resources without thinking twice about it. It was an obvious choice back then: we needed to build fast, the team was tiny, and our backend & API was built on FHIR. The people in charge of the frontend app were very familiar with the FHIR API codebase as well.

FHIR is a worldwide standard. It needs to cover thousands of use cases: doctor’s appointments, allergies, diagnostics, hospitals, … It needs to store personal information for patients around the world, including parts of the world where you can have 0, or 5 first names.

Long story short, a FHIR Patient does not look like that:

Alas! This is much closer to reality:

As you may have noticed, it contains A LOT of data, some of which I won’t ever need. I have to write a lot of logic just to get the patient’s national ID, for example:

The result is :

  • Copious amounts of code and a great deal of data processing in the frontend. Every time we want to display a patient’s name or ID, we have to filter arrays and access many nested properties. On top of that, although we use Typescript, standard FHIR typings are loose by nature, and most properties are optional by default. Sure, we have written utils for those things. But the user’s computers still have to do the work.
  • New developers struggle for a while because understanding FHIR takes time. Lifen is a fast-growing company, and teams need to specialize. Separation of concerns is increasingly important in this context. For a frontend developer wanting to contribute, having some knowledge of FHIR has to become a bonus, not a requirement. The current architecture of the project prevents us from making progress on this point, because contributing to the codebase is almost impossible without any basic knowledge of FHIR.
  • Maintenance is arduous and time-consuming. New feature development is slow.

Looking for alternatives

The facts are now established. Maybe building our frontend app on the FHIR standard wasn’t such a great idea after all.

In 2019, a first push is made by some developers within the frontend team. The idea is to build an API Gateway that would act as a middle man between the frontend client and the FHIR API. It would fetch the FHIR resources, transform them into a more client friendly format and serve them to the client.

After a short but lively debate, the idea is shut down. Here are a few of the reasons involved in the decision:

  • we wanted everybody at Lifen to be able to speak FHIR
  • the API team had no bandwidth to maintain this new middleware
  • the frontend code would have to be written practically from scratch because FHIR is everywhere in the codebase. Obviously, we had no bandwidth for that, either.

Making it happen

In April of 2020, right after the Covid outbreak, we released an app dedicated to Covid patients management. This new app pushed us to speed up on a project we’d had in mind for a long time: creating an healthcare app store.

The frontend team started to work on this new project, with a new git repository. And this time, we would need a backend for our app. The middleware idea, which we had enough time to mature, was now back on the table. This time, we went through with it, with a few adjustments:

  • This time, it would be the frontend team’s responsibility. This solves two of the initial drawbacks: frontend developers still have to speak FHIR because they need to be able to work on the backend (though it might change as the team scales further), and the API team does not have to get involved.
  • It would go much further than just an API Gateway. The new server would hold most of the business logic, which was originally concentrated in the client.

The project’s architecture

The codebase is a mono-repository made of multiple packages:

  • server
  • client
  • common, where we write utils and typings that are shared across all packages.
  • various other packages (UI Kit, authentication utils, etc.)

This structure is particularly interesting for us because it allows us to write less code, and most importantly to share Typescript types across client and server. Since this strict contract interface links the two apps, we could swap our entire data structure in the backend without a single update in the frontend, as long as this contract is respected. In the case of a change in the types for any entity returned by the server, Typescript gives us a list of all the tweaks needed to make it work in the frontend.

The common package also holds precious utils and constants, as well as factories that we use to generate fake data for both frontend and backend automated tests. Those factories also make use of shared typings, so they are systematically updated when we make type-related changes.

Deep-dive into the server’s architecture

The server’s architecture is pretty standard and easy to understand. This was a requirement for the project. The business logic is complex and there is no way around it. There is no need to add technical complexity on top of it.

The architecture is largely inspired by the principles of The Clean Architecture.

Controllers

Controllers are the entry points to our application. Their job is to parse query params and data, call a use case with the parsed parameters and return the result.

Use Cases

A use case always corresponds to a feature: create a patient, send a message, update a contact, etc. The use cases are where all the application logic is located. They are all the things our application can do. This is where we validate payloads, where we check application rules are correctly observed.

Repositories

In our case they are basic wrappers around the FHIR SDK and other internal APIs we might use. They are also in charge of calling adapters before they return any data. This way, they are the only application layer that is aware of FHIR resources or any other storage-specific data format.

Adapters

Adapters are responsible for the conversion of FHIR entities into our own Patient, Doctor, Document, (…), interfaces. They have an enormous responsibility, so they are tested thoroughly (with snapshots + unit tests for more specific cases).

They do not intend to keep 100% of the FHIR data during the conversion. The stuff we don’t need is not included in the new interfaces.

For instance, here is the Typescript interface for our own Patient entity.

Pretty neat compared to what we had previously!

The Great Migration

During the spring and summer of 2020, we had the opportunity to put this new architecture to the test by building a handful of small-scale applications for our new store (a healthcare professional directory, a patients listing, and the store itself). Happy as we were with the results, we decided to migrate our legacy app, the one responsible for sending millions of medical reports every month, to this new codebase.

Given the state of the legacy codebase, we would have to rewrite the code from scratch. It was an opportunity as much of a challenge: a chance to comply with a much stricter set of rules, including a cleaner architecture, and better separation of concerns.

After a year of work, we’re now getting ready to release a new version of our app, rebuilt from scratch based on this new paradigm. 🥳 Tell us in the comments if you want to know more about this journey.

So… Should you use FHIR in your frontend apps?

Right, this story was meant to answer a question. So, should you use FHIR — or any other storage-friendly/frontend-unfriendly format — in your client apps?

I’ll make it short and simple: Unless you’re building an internal tool and need to display the raw payloads of resources, investing in a robust way to convert complex data into something simpler will almost always be worth it.

Other questions you might have

So where does Lifen stand now in terms of being “FHIR Native” ?

Well, our commitment to FHIR still stands. We are convinced that storing persisted state in FHIR format provides significant value to us and our customers. What we have learned along the way, though, is that FHIR as a data model for the presentation layer might not be the best fit.

Was it a waste of time to dismiss the API Gateway idea back in 2019?

Maybe? But, ultimately, the timing wasn’t right. Sometimes, we need to make decisions based on human dynamics rather than only considering what makes the most sense technically speaking. Back then, we probably didn’t have the right expertise in the team to lead such a project successfully, and the product was not as mature as it is right now.

Can’t you just transform fetched data in the client ? You don’t need a backend application to do that.

True… But:

  • the server acts as a proxy: this is very welcome because web domains need to be individually whitelisted by hospitals. Historically, we asked them to whitelist 20+ domains. If they missed just one, it could have an impact on the proper functioning of the app.
  • it means less data processing in the frontend: our clients’ hardware is aging so the less processing the better, and the frontend code is easier to read and maintain.
  • separation of concerns: Our frontend does not speak FHIR. We could swap our entire data structure and we would not need to change a single line of code in the frontend!
  • other internal frontend apps can (and do) make use of this server. For instance, our patient portal, a separate React app, sends debug logs to internal tools through the server.
  • the team could easily be split in two as it scales, further down the line. Some would work on the server, others on the frontend client.

--

--