
Couchbase x Medblocks - Building a NoSQL FHIR Server
We walk you through building a FHIR server on top of Couchbase's NoSQL database, covering challenges, technical decisions, architecture, and local setup.
August 14, 2026
If you’ve built anything on FHIR, you’ve hit the test data problem.
EHR sandboxes give you a few examples of each resource, all suspiciously clean. Maybe you asked ChatGPT or Claude to generate synthetic patients instead, and it gave you exactly what you asked for. Clean, plausible data that looks nothing like production. So your app works fine against the sandbox, then breaks the first time it sees a patient with three comorbidities, a twenty-year medication history, and a decade of records typed in by hand, with names spelled three different ways.
Luckily, synthetic data generation turns out to be a solved problem. There’s a project called Synthea, funded by the US government and built by MITRE, that can generate millions of synthetic data points that aren’t just syntactically valid FHIR. They’re clinically meaningful too. Nothing in the record floats free. A prescription traces back to a diagnosis, the diagnosis to an encounter, the encounter to symptoms, and the dates all line up.
The catch is that everything Synthea generates is American. The output format follows US Core, the national FHIR profile of the US. And the patients inside it are American too: they carry social security numbers and driver’s licenses, they get sick at the rates the CDC publishes, and the record contains exactly the data points US Core asks for. If you’re building in the US, this is perfect. But anywhere else, you’re testing against data your systems will never see.
A few years ago, a German organization called HiGHmed asked us to fix that. To understand what fixing it involved, you need to see how Synthea actually works first.
It starts with a patient at birth and runs them forward until they die. Along the way, things happen to them. They get sick, they recover, they go get checked at a hospital, they develop a chronic condition, they get prescribed medications. All of this is modeled through Synthea’s modules.
A module is a state machine for a disease. Formally, this is Synthea’s Generic Module Framework. Each module is a JSON file describing states and the transitions between them. A module defines when a condition might start, and it assigns probabilities based on the patient’s characteristics like age and sex. From there it walks through the probability of symptoms, and the probability of the different treatment pathways a doctor might take. You can browse these modules, edit them, and build new ones in a web-based Module Builder without writing any code, which is how clinicians contribute modules without touching the engine.
Every patient runs through hundreds of these modules. It’s basically a Monte Carlo simulation, meaning the realism comes from randomness at scale. You’re randomly running patients through a maze of hundreds of conditions they could develop, and what comes out the other end is a population where patients have multiple diseases and comorbidities, the way real populations do.
Every probability in these modules comes from real disease distributions in the US, drawn from the CDC, NIH, and other research sources. The appendicitis module, for example, assigns males and females different risks of developing it, following the incidence reported in the literature, and then walks the patient through to the appendectomy.

Many modules cite the published papers their statistics came from. And because each module mirrors real incidence rates, the population that comes out mirrors them too. Run analytics across a generated dataset and the disease prevalence looks close to what you’d find in the actual US population. The 2018 JAMIA paper that introduced Synthea walks through the full design, down to the framework they built for producing a realistic synthetic EHR without ever touching real patient data.
FHIR only shows up at the very end of all this. The simulation itself doesn’t know anything about FHIR.
Synthea runs in two stages: the simulation builds each patient’s complete history first, and exporters then translate that finished history into whichever format you need. FHIR resources are one option; the same history can just as easily come out as CSV tables, C-CDA documents, or an OMOP export for research databases.
That separation is the whole reason the German project was possible, and we’ll come back to it.
This layering is also what separates Synthea from anything an LLM will give you. When ChatGPT generates a patient, it’s producing something that looks plausible. When Synthea generates a patient, every entry in the record exists because a simulated event produced it. A simulated patient can carry a condition for months before their record mentions it, because the condition only enters the record when they turn up at a hospital and a doctor finds it. An LLM has no reason to produce a gap like that. It writes what records usually look like. The simulation produces it because, in the simulation, that’s what happened.
Synthea is American because its funding is. It’s built by MITRE, a not-for-profit that operates federally funded research centers for the US government, and its development has been paid for by US federal agencies. When ONC launched a project in 2019 to expand Synthea, the new modules it funded were for American research priorities, things like opioid use, pediatric populations, and patients with complex care needs.
That shows up most concretely in US Core. Base FHIR is deliberately loose. A resource like Patient or Observation has dozens of optional fields, and the standard doesn’t say which ones you must fill or which coding systems you must use. A profile tightens that, and every country or organization tightens it differently. US Core is the set of profiles US systems are expected to follow, and it gets specific. A lab result must carry a LOINC code, a patient record must support race and ethnicity through extensions defined for US reporting, and identifiers follow US systems. Two implementations that both speak FHIR can still be unable to exchange data; profiles are what make them actually compatible. And Synthea generates data that matches the US Core profiles precisely.
HiGHmed, who brought us this problem, sits on the other side of that compatibility line. HiGHmed is a consortium within the Medical Informatics Initiative, the national program in which German university hospitals are building the infrastructure to share patient data for research across sites. To do that, they settled on their own FHIR profile: the MII core data set, their version of US Core, basically.
And they needed realistic test data to build against, because the one thing a research initiative cannot easily get is real patient data. Synthea already existed, its engine was proven, and it was open source. It was the obvious tool. It just had to stop speaking US Core and start speaking German.
Synthea ships with an option called Flexporter for profiles that sit close to US Core. If you just need minor changes in how something is represented, you remap the output and you’re done.
However, the MII profiles are not closely aligned with US Core.

Take the patient’s identifiers. The American ones Synthea generates mean nothing in the German system; an MII patient is identified by their health insurance number, the GKV number for the roughly 90% of Germans on statutory insurance, and the profile defines exactly how that identifier must be coded.
Or take a death. In US Core, it’s a field on the Patient resource itself, deceased set to true, with a date-time if known. Two lines in the record. In the MII profile, the same fact becomes an entire separate resource. A Vitalstatus Observation carries its own LOINC code, records the last point in time the patient was known to be alive, and gets created fresh at every hospital admission and discharge. A field on one side, a stack of Observations on the other, for the same fact about the same patient.

Then there are the parts of the MII core data set that don’t exist in US Core at all, because they were built for research. Patients must have a documented Consent before their data enters a research dataset, and a ResearchSubject resource ties the patient to the study they’re enrolled in. Even the clinical data goes deeper than care requires. Microbiology results, for instance, are profiled down to a level of detail only a researcher would ask for. All of this exists in base FHIR, but US Core doesn’t profile it, which means Synthea had no simulation for any of it.
Then there’s the demographic layer underneath. The statistics of disease occurrence in the German population differ from the American ones baked into Synthea’s modules.
The one genuinely easy part is the meta.profile URL, the label on every exported resource declaring which profile it conforms to. Swapping it means changing this:
http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient
to this:
https://www.medizininformatik-initiative.de/fhir/core/modul-person/StructureDefinition/Patient
That’s the kind of change Flexporter handles on its own. But everything else on the list is a problem of what data gets generated in the first place, and that’s decided during the simulation. Flexporter never touches the simulation. It can only rename what comes out.
So we modified Synthea itself. We changed the modules and the engine so the simulation produces German patients, and we rewrote the exporter so their records come out as MII-conformant FHIR.
Modifying Synthea was easier than it sounds. The codebase keeps the simulation and the export separate, so each could be changed on its own.
The module builder side is just web UI work. Someone who knows enough about the domain can go through and build the modules without touching the engine. The compiler side took the most effort. That’s the part that turns a finished simulation into actual FHIR or CSV, and it had to learn the MII’s rules.
We started with the basic modules of the MII core data set: person, diagnosis, laboratory findings, case, and medication. “Case” is the MII’s name for the encounter module, after the German word “Fall”, a hospital stay. In the generator, each module is a card you switch on or off, so if you only need lab data, you generate only lab data.

The biggest single change was the FHIR exporter. Out of the box, Synthea stamps US Core profiles onto every resource it writes. We rewrote the R4 exporter (one class, FhirR4.java) to stamp the MII profiles instead, so a lab result comes out pointing at the MII lab observation profile, a consent resource points at the MII consent profile, and so on.
The whole thing ships as three parts stacked together.
A Vue.js front end where you describe the population you want, size, seed, age range, gender.
A thin Spring Boot backend with a single /generate endpoint that translates your form into Synthea’s config and runs it.
And underneath, our forked Synthea engine doing the actual generation.

Generating a few thousand patients takes a while. It’s running the full simulation, so the engine streams its progress logs to your browser over a WebSocket while it works, and when it finishes, the output folder is zipped in memory and streamed back as a download. Depending on the population size this gets massive: sometimes gigabytes, and we’ve seen 50GB of output. The zip compresses it well, but the practical pattern we see is that people generate a dataset once and reuse it across multiple downstream tasks.

We also added deterministic output, which the original Synthea doesn’t have. There’s a way to control the randomness inside the Java virtual machine by setting a seed, so we exposed a seed value in the UI. Provide the same seed, get the exact same output, every single time.
In practice, the seed becomes a way to share entire datasets without moving any data. Say you’re testing a validator against a million generated resources. Instead of shipping collaborators the gigabytes, you send them the seed, they run the generator themselves, and everyone is now working on identical data.
This is also something you fundamentally cannot get from an LLM. You can’t ask ChatGPT for the same patient twice.
HiGHmed had one more requirement, and it turned out to be the part we enjoyed most. Alongside the valid data, they also wanted the generator to produce broken FHIR, in two flavors.
The first was syntactically wrong FHIR, to check that their validators were working. We did this with fuzzing, which means taking a good FHIR resource and mangling it. A field name gets a character it shouldn’t have, so resourceType might come out as resourceTypes, a value gets corrupted somewhere else, then check whether the validator catches it.
The second was semantically wrong FHIR, meaning resources that are perfectly valid FHIR but clinically impossible. A patient with two appendectomies, when you only have one appendix. A medication administered three weeks after the patient’s date of death. A biologically male patient with a pregnancy diagnosis. HiGHmed wanted to make sure their downstream systems could process records like these and catch them.
Generating these took surprisingly little extra machinery. We built modules with nonsense scenarios. Modules don’t know what the right clinical thing to do is. They’re just state machines, and they’ll walk a patient through whatever pathway you define. So we defined the impossible ones, and the engine treated them like any other disease. It ran the male patient through the pregnancy module, recorded the events, and exported the whole thing as clean, profile-conformant FHIR. The record is perfectly structured. The medicine inside it is nonsense.
Valid test data only tells you your system works on the happy path. Broken data tells you whether your validator actually functions.
Synthea is one of the best tools available for generating realistic patient data, in any format. And if the format is FHIR, there’s a path to make it work with any implementation guide.
The structure that makes this possible is the two stages from earlier. The simulation stage runs patients through real clinical journeys, and this is the part that would be hard to recreate. Hundreds of modules, built on published statistics, refined over years. Starting from scratch, this is where all the work would go, and for anyone adapting Synthea, it’s already done.

The export layer is the part you can change. Write an exporter for your own implementation guide, or for a format you’re inventing, and Synthea will transform the same realistic clinical histories into whatever shape you need. The engine doesn’t care whether the output is FHIR bundles, CSV tables, or rows for a database. The clinical history is already there, and the exporter just decides what it looks like on disk.
Where does AI fit into this? The two stages give a clean answer. LLMs are weak exactly where the simulation is strong, in reproducibility and clinical ordering, so generating the records themselves is the wrong job for them. Writing modules is a much better one.
A module is a small JSON state machine built from published statistics, which is the kind of structured drafting LLMs handle well, and MITRE has since explored exactly this, using generative AI to draft Synthea modules that clinicians then review. The drafted module feeds the deterministic engine, so the output stays reproducible no matter what wrote the module. When this project was built, AI wasn’t at that level, so the definitions were handwritten. Built today, that part of the work would look very different.
Two broader takeaways from the project.
First, anybody publishing a FHIR implementation guide, whether it’s a national profile or one for a single company, should ship a Synthea module or extension alongside it, one that generates realistic data for their own population against their own profiles. Without it, the developers building against your guide are starting from nothing.
Second, government-funded software should be open source. Synthea only exists because US public money paid for it, and this project only exists because the result was open. A tool paid for by the public went back to the public, and a German research initiative got to build on top of American public investment. Other governments might want to take that as inspiration, or just fork Synthea and add modules that fit their own populations.
None of this work matters without one thing at the start of it, though. Realistic data has to exist before anyone can build against a profile, and most implementation guides still ship without it.
If you’re working on a national profile, or a profile for your own company or your own small group, get in touch. We’d be happy to talk through what synthetic data for your implementation guide would look like. Get in touch here.
The HiGHmed synthetic data generator itself is open source. Everything described here is in this repo, ready to run against your own profiles.

We walk you through building a FHIR server on top of Couchbase's NoSQL database, covering challenges, technical decisions, architecture, and local setup.

Catalonia wanted an openEHR Clinical Data Repository for 8 million people. Medblocks worked with IBM and vitagroup to design the sync layer for hospitals.

Here we explore a real-world project we did with GE HealthCare, where we tackled the challenges of data normalization in oncology care.
No comments yet. Be the first to comment!