Skip to content
FHIRVendors

FHIR Server Architecture: Storage, Search, Scale and Build Options

What sits inside a production FHIR server, the design choices that determine its performance and cost, and how to weigh open-source, managed cloud and commercial platform options.

ArchitectureUpdated 5 min readBy the FHIR Vendors Editorial Team

Key takeaways

  • A FHIR server is several systems in one: a resource store, a search engine, a version history, a validator, a terminology service and often an event and bulk-export pipeline.
  • Storing resources as JSON documents keeps writes simple and faithful; relational mappings can make analytics easier but add mapping cost. Most servers combine stored resources with separate search indexes.
  • Search is usually the hardest part to scale, because FHIR's query language supports chaining, includes, modifiers and token, date and quantity semantics.
  • Bulk Data $export, subscriptions and multi-tenancy are architectural decisions to make early, not features to bolt on later.
  • Building on open source such as HAPI FHIR, using a managed cloud service or buying a commercial platform trade control against operational effort in different ways.

Core components of a FHIR server

Implementing the FHIR RESTful API means handling create, read, vread, update, patch, delete, history, search, batch and transaction interactions, plus whatever operations your use cases need. Behind that API, a production server typically contains:

  • A resource store holding current and historical versions.
  • A search index that extracts values for each supported search parameter.
  • A validation layer that checks resources against base definitions and profiles.
  • A terminology service for code validation and value set expansion.
  • Security integration: an OAuth 2.0 authorization server, scope enforcement and audit (see FHIR security).
  • Optional event and export pipelines for subscriptions and Bulk Data.
  • A CapabilityStatement that accurately describes what the server supports.

Storage approaches

ApproachHow it worksStrengthsTrade-offs
Native JSON documentEach resource version is stored as a JSON document (in a document database or a JSON column) with separate index tables or an external search engineFaithful round-tripping, extensions and new resource types need no schema change, straightforward versioningSearch depends entirely on the indexes; analytics queries need extraction
Relational mappingResources are decomposed into normalized tables per resource and elementFamiliar SQL for reporting, strong typing, joinsLarge mapping effort, schema changes for profiles and new versions, extensions are awkward, risk of lossy round-trips
HybridRaw resource stored as a blob or JSON; key elements projected into relational index tablesBalances fidelity and query performance; used by many mature serversTwo representations to keep consistent; indexing rules to maintain

For most teams the hybrid pattern is the pragmatic default. A pure relational model makes sense mainly when the FHIR API is a facade over an existing clinical database, in which case the challenge shifts to mapping and keeping the facade conformant.

Search indexing and versioning

FHIR search is expressive: string, token, reference, date, number, quantity, URI and composite parameter types, each with its own matching rules, modifiers such as :exact and :missing, prefixes such as ge and lt, chained and reverse-chained parameters, _include and _revinclude, sorting and paging. Each search parameter is defined by a FHIRPath expression that tells the indexer which values to extract.

  • Index only the parameters your implementation guides require plus those your clients actually use; every index adds write cost.
  • Date indexes must handle ranges and partial precision, because FHIR dates can be a year, a month or an instant.
  • Token search needs both system and code, and many clients search by code alone, so index both forms.
  • Paging should use stable cursors so results do not shift while a client is iterating.

FHIR treats each update as a new version with its own versionId, retrievable via vread and the _history interaction. Store versions immutably, support conditional updates with If-Match for optimistic locking, and decide your retention policy for history up front, because it can dominate storage growth.

Terminology services and validation

Validation against profiles requires terminology: checking that a code belongs to a bound value set means expanding that value set, which may draw on large code systems such as SNOMED CT or LOINC. Servers either embed a terminology engine or call an external terminology server that implements operations like $expand, $validate-code, $lookup and $translate.

Full profile validation on every write is expensive. Common patterns are to validate synchronously at the API boundary for external writes, validate asynchronously for bulk loads, and cache value set expansions aggressively. Whatever you choose, be explicit in your documentation, because clients will assume that data returned by the server conforms to the profiles it advertises.

Subscriptions and Bulk Data export

Event notification and population-level export both push work off the request path, so they need asynchronous infrastructure: a durable queue, workers and somewhere to stage output. Topic-based subscriptions, described in the R4 vs R5 guide, are easier to implement efficiently than R4 criteria-based subscriptions because the server defines the triggers.

The Bulk Data Access implementation guide defines $export at system, Patient and Group level. The client starts a job, polls a status URL and then downloads NDJSON files, one per resource type.

GET /fhir/Group/diabetes-cohort/$export?_type=Patient,Observation,Condition HTTP/1.1
Host: fhir.example.org
Accept: application/fhir+json
Prefer: respond-async
Authorization: Bearer eyJhbGciOi...

HTTP/1.1 202 Accepted
Content-Location: https://fhir.example.org/bulkstatus/7c1e2b
Kicking off a Group-level Bulk Data export. The client polls the Content-Location URL until the job completes.
  • Generate exports from a read replica or snapshot so they do not degrade transactional traffic.
  • Stage output in object storage with short-lived, access-controlled download URLs.
  • Support _since for incremental exports; it is what makes recurring population feeds affordable.

Multi-tenancy and scaling

Platforms serving several organizations must decide how to isolate tenants. Options range from separate databases per tenant (strong isolation, more operational overhead) to partitioning within a shared database (efficient, but every query and index must be partition-aware) to logical separation through security labels or compartments (cheapest, riskiest). Tenant identity is often expressed in the base URL, which keeps references and CapabilityStatements clean.

  • Keep the API tier stateless and scale it horizontally behind a load balancer.
  • Scale reads with replicas and caching of conformance resources and terminology expansions.
  • Watch transaction Bundles: large ones hold locks and can dominate write latency.
  • Load-test with realistic search patterns, including _include and chained queries, not just simple reads.

Operational visibility deserves the same attention as throughput. Track latency and error rates per interaction type and per search parameter, not just per endpoint, so a single slow chained query does not hide inside an average. Log OperationOutcome issue codes, monitor queue depth for subscriptions and exports, and rehearse restores from backup, including history and search indexes, before you need them.

Open source, managed cloud or commercial platform

Few organizations should write a FHIR server from scratch. The real choice is between three paths, each with a different balance of control and effort:

OptionBest whenWatch out for
Build on open source (e.g. HAPI FHIR, the widely used Java implementation)You need deep customization, on-premises deployment or control of the code, and have Java and DevOps capacityYou own upgrades, security patching, scaling, uptime and validation tuning
Managed cloud FHIR serviceYou want a conformant API quickly with the provider handling infrastructure, and your workloads already run on that cloudFeature gaps against the spec, limits on custom operations and search parameters, data residency and egress costs, provider dependency
Commercial FHIR platformYou need built-in features such as consent management, data quality tooling, analytics or vendor support with defined SLAsLicensing cost, proprietary extensions and migration effort if you leave

Evaluate each against the same checklist: FHIR versions and implementation guides supported, search parameter coverage, SMART and Bulk Data support, subscriptions model, validation behaviour, tenancy, observability, backup and export of your own data. The FHIR server providers directory lists options, and teams that need a custom build can review FHIR server development services.

Test conformance, not marketing

Run the relevant Inferno test kit and your own representative queries against any candidate before committing. Capability claims and real behaviour often differ at the edges.

Frequently asked questions

Should FHIR resources be stored in a relational database or a document store?

Either can work. Most mature servers keep the full resource as JSON for fidelity and maintain separate index structures for search. Fully relational mappings make sense mainly when FHIR is a facade over an existing schema.

What is the hardest part of a FHIR server to scale?

Usually search. Chained parameters, _include and _revinclude, sorting and large result sets put far more load on the database than simple reads and writes, so index design and query planning matter most.

Do I need a separate terminology server?

If you validate against profiles bound to large code systems, or need value set expansion and code translation, a dedicated terminology service is usually worth it. Small deployments can embed one.

Is HAPI FHIR production-ready?

HAPI FHIR is an open-source Java implementation used in many production systems. Production readiness depends on how you deploy, secure, tune, monitor and maintain it, which is your responsibility when self-hosting.

How does Bulk Data export avoid overloading the server?

It is asynchronous. The server accepts the request, processes it in the background (ideally from a replica) and writes NDJSON files the client downloads later, so the work does not block interactive API traffic.

References

  1. HL7 FHIR R4: RESTful API
  2. HL7 FHIR R4: Search
  3. HL7 FHIR R4: Terminology service
  4. HL7 FHIR R4: Validation
  5. HL7 Bulk Data Access IG
  6. HAPI FHIR

FHIR® is a registered trademark of HL7 International. This guide is independent educational content and is not legal or compliance advice.

Related guides

Related FHIR services

Find the Right FHIR Integration Partner for Your Healthcare Project

Tell us what you need to build or connect. We review every request and share a short list of relevant vendors from our directory — no obligation.