In this blog post, I’ll be detailing our journey in implementing a full FHIRserver on top of our existing FHIR infrastructure. In a previous blog post,
detailed our initial FHIR implementation and I’d recommend giving that a read if you’re interested in the gory details. For reasons that I’ll detail fully in the next section, we needed to evolve our implementation into a fully-fledged FHIR server. At the end of this post, I’ll also go over the technical implementation of our HAPI FHIR server, and some learnings and next steps if you’re interested in building your own FHIR server from scratch!
Since our initial FHIR implementation, our need for additional FHIR support has proliferated — in order to unlock things like FHIR data quality testing and connecting to the Health Information Exchange (HIE), we needed more elements of a working FHIR server, especially since several desirable external systems like these utilize FHIR APIs to communicate. More importantly, as our team and codebase scales, we’ve been experiencing an increasing need to evolve our FHIR implementation past the initial infrastructure we initially implemented. In particular:
It was a relatively short search. In addition to the considerations from above, we really valued:
HAPI FHIR checked all the boxes for us (particularly the Plain Serverimplementation). In short, the plain server provides a structure by which the developer can implement the FHIR REST operations on top of a pre-existing schema, and HAPI FHIR will handle wiring up the routes. In our case, we had an existing FHIR schema (detailed in a previous blog post), and we could build out the Java entities and HAPI FHIR infrastructure on top of that to get to a functional FHIR server. HAPI FHIR was perfect for us except that…
Our teamwide Java expertise was low, given that we had been developing our backend purely in Python since Curai’s inception. Compounding things, I could not find a single end-to-end, minimal HAPI FHIR implementation tutorial, which made it especially tough to get started. After a few weeks of Googling, coding, and cryptic errors, I was able to piece together several different tutorials to make a minimal example of a HAPI FHIR plain server. Obviously, our production implementation has a lot more bells and whistles, but these will vary from use case to use case so I think it’s most helpful to see a minimal example that you can eventually build on top of. When building your own server, three resources that I found particularly helpful are the FHIR community board, HL7 Confluence page, and DevDays presentationsfrom years prior.
Before we jump into the code, below is a diagram of how each part of our FHIR infrastructure exists in our backend right now (simplified for the Patient FHIR resource type). The “Python” box is what our current FHIR systems operate on as described by Viggy in his blog post.The “Java” box is what we’re implementing in this tutorial. As shown, both are simultaneous consumers of the postgres table fhir_patient, at least until we migrate fully to HAPI FHIR (which will fully deprecate the “Python” box). Let’s get started with how to implement the “Java” box! Step-by-step instructions are below and the full code is here!
Author’s note: for each step, please click on the (Code) link in the title to view the code changes required for each step before running the commands specified in the description!
The first step to setting up our FHIR server is to set up a base Java webapp. To do this, head to Spring Initializr and add “Spring Data JPA”, “Spring Web”, and “H2 Database” (the link provided should have all of them already). Generate and download the bundle, and you have a base web server that can connect to a real H2 database. Later we will connect it to the real Postgres database.
Now we’ll configure our Spring Boot server to work with HAPI FHIR’s server infrastructure. Note that HAPI FHIR’s REST API is configured differently than the typical Java Spring REST API routes, which is why we need to do this step.
Open up the project in IntelliJ (or your favorite Java IDE) and apply the code changes in the section title.
Running ./mvnw spring-boot:run from the project’s directory will pop up a server, and you should be able to download the server’s (empty) capabilities at localhost:8080/metadata. Right now, it should indicate that there are no actual capabilities supported because we haven’t implemented anything yet! If you’re getting an error, make sure you have a JDK installed!
The HAPI FHIR Context is what we use to parse strings and bytes into Java objects (and back). It’s the core of HAPI FHIR, and we’ll need to use it in every piece of code that relates to HAPI FHIR. Since it is expensive to create, we want to create it just once, and access it from everywhere it is needed. Hence static methods!
Let’s make our FHIR server do something! In order to create an endpoint for a resource, we need to implement a provider for the resource. Then, we need to create a method and annotate it with the appropriate HAPI FHIR decorator. This will automatically configure the server to call that method when the route corresponding to the method is hit via the API.
Even though we’re not connected to a real database yet, we can still return some fake data to test our HAPI FHIR server. In this case, we’ll create a fake Patient and implement the read endpoint so we can view the patient.
In this case, the @READ annotation configured a GET operation to the Patient endpoint by id, which is accessible at localhost:8080/Patient/1 (sub in any id — we just need a dummy one to trigger the read endpoint) and you should see an XML file with Lord Farquaad’s Patient data.
After applying the code, going back to localhost:8080/metadata, we should now see that the server supports Read for Patients. HAPI FHIR configured this automatically, and all future providers and methods will also be noted in this CapabilityStatement automatically.
Now that we know how to interact with the HAPI FHIR to get some dummy data, we can connect the API to an actual Postgres Database. You’ll need to create a local Postgres database with a username and password to supply to the application.properties config.
As noted in Viggy’s FHIR post, our FHIR data follows a schema where the entire resource is stored as a jsonb column. Create a fhir_patient table in the database with a jsonb column titled resource (and an id primary key column) and seed the table with a fake Patient JSON.
We need to add in support for jsonb columns, since Hibernate (the Java ORM) does not support it natively — to do this, we’ll use a library called Hibernate-Types!
After applying this diff, when querying for the Patient resource at localhost:8080/Patient/1, you should get a serialization error. Why? The Java object serialized by Jackson (the default serializer for Hibernate) cannot be cast into a HAPI FHIR Patient instance. We need to configure the serializer to use the HAPI FHIR parser instead. Good thing we can use the HAPI FHIR context that we configured in step 2!
With this, we tell Hibernate + Jackson to serialize/deserialize Patient types with the custom ObjectMapper that we provide. Going back to localhost:8080/Patient/1 should result in the exact JSON that you seeded the table with!
As I mentioned before, this is an intentionally barebones implementation of a FHIR server meant for you to customize to your own needs — we’re only supporting one operation for one resource, and the code isn’t very generic (it would be tedious to implement the 19 other resources that Curai currently uses). If you’re building your own FHIR server on top of this, here are some areas of work that we’ve built on top of this code:
You may even see some of these as blog posts from us in the coming months!
If you found the contents of this blog post interesting, and / or are interested in working at a health tech company that dreams of changing the world and bringing the world’s best healthcare to every human being on the planet, Curai is hiring! Check out our careers page here.