← All articles

FHIR Meets Graph Databases: Exploring Healthcare's Natural Network Structure

One of the more differentiating things we do at 1Putt Health is help teams think clearly about the data architecture underneath their FHIR implementations — not just how to pull resources from an endpoint, but how to model and query the clinical relationships those resources encode. The work described here came out of building CLaiR, a clinical AI assistant built on FHIR + graph infrastructure, and it fundamentally changed how we think about healthcare data architecture.

FHIR Data Is Already a Graph

When you start building on FHIR and look at the data model seriously, something becomes obvious: this is not tabular data with a foreign key bolted on. It is a graph.

A Patient resource links to Encounter resources. Each Encounter references Observation resources — lab results, vital signs, assessment findings. Those Observation resources link to Practitioner resources (who ordered or documented them), Organization resources (where they were performed), and Condition resources (the diagnoses they inform). MedicationRequest resources connect back to the Patient, the ordering Practitioner, and the Encounter in which they were created. CarePlan resources connect conditions to goals to activities to the team of practitioners responsible for execution.

None of these references are incidental. They are the actual semantic content of the clinical record. The relationships between resources are not metadata — they are the information. A sodium level of 138 means almost nothing in isolation. In context — this patient, this encounter, this trend over 90 days, this concurrent medication list — it means a great deal.

In a relational database, reconstructing those relationships requires joins. Lots of joins. Joins that get expensive fast as the clinical record grows. And the query patterns that matter clinically — find all patients whose sodium trended downward over 12 weeks while on this medication class, identify encounters where this diagnosis appeared but this intervention was absent — do not map cleanly to SQL.

Graph Databases Reveal What Relational Databases Hide

When you import FHIR resources into a graph database like Memgraph or Neo4j, you stop fighting the data model and start working with it.

Each FHIR resource becomes a node. Each reference field in a resource becomes an edge. The FHIR resource type becomes the node label. The reference relationship type becomes the edge label. The graph then accurately mirrors what the FHIR spec actually describes: a connected network of clinical entities.

A Patient node in Memgraph might look like this:

// Create Patient node
CREATE (p:Patient {
  id: 'pat-001',
  birthDate: '1975-09-20',
  gender: 'female',
  mrn: 'MRN99999'
})

// Create Condition node
CREATE (c:Condition {
  id: 'cond-001',
  code: '44054006',
  display: 'Type 2 diabetes mellitus',
  system: 'http://snomed.info/sct',
  clinicalStatus: 'active',
  onsetDate: '2019-03-15'
})

// Create the relationship
CREATE (p)-[:HAS_CONDITION {recordedDate: '2019-03-15'}]->(c)

And a query that would require four joins in SQL becomes a natural graph traversal:

// Find all patients with Type 2 diabetes who had HbA1c > 9 in the last 90 days
// but have no recorded endocrinology referral
MATCH (p:Patient)-[:HAS_CONDITION]->(c:Condition {code: '44054006'})
MATCH (p)-[:HAD_OBSERVATION]->(obs:Observation)
WHERE obs.loincCode = '4548-4'  // HbA1c
  AND obs.valueQuantity > 9.0
  AND obs.effectiveDate > date() - duration('P90D')
WHERE NOT (p)-[:HAS_REFERRAL]->(:Referral {specialty: 'endocrinology'})
RETURN p.id, p.mrn, obs.valueQuantity, obs.effectiveDate
ORDER BY obs.valueQuantity DESC

This query expresses clinical intent directly. In a relational schema, the equivalent requires multiple joins across patients, conditions, observations, and referrals, with subquery logic to express the absence of referral. The graph version is readable by a clinical informaticist who has never written a database query.

Importing FHIR Into Memgraph

The import process for a FHIR Bundle follows a consistent pattern: parse each resource, create a node for it, then walk the reference fields and create edges. Here is a simplified FHIR-to-graph importer in TypeScript:

import Memgraph from 'neo4j-driver' // Memgraph uses the Bolt protocol, compatible with neo4j-driver

interface FhirResource {
  resourceType: string
  id: string
  [key: string]: unknown
}

async function importFhirBundle(bundle: { entry: { resource: FhirResource }[] }, session: any) {
  for (const entry of bundle.entry) {
    const resource = entry.resource
    const nodeId = `${resource.resourceType}/${resource.id}`

    // Create or merge the resource node
    await session.run(
      `MERGE (n:${resource.resourceType} {fhirId: $fhirId})
       SET n += $properties`,
      {
        fhirId: nodeId,
        properties: flattenFhirResource(resource),
      }
    )

    // Walk reference fields and create edges
    const refs = extractReferences(resource)
    for (const ref of refs) {
      await session.run(
        `MATCH (source {fhirId: $sourceId})
         MATCH (target {fhirId: $targetId})
         MERGE (source)-[:REFERENCES {field: $field}]->(target)`,
        {
          sourceId: nodeId,
          targetId: ref.reference,
          field: ref.field,
        }
      )
    }
  }
}

function extractReferences(resource: FhirResource): { reference: string; field: string }[] {
  const refs: { reference: string; field: string }[] = []

  function walk(obj: unknown, path: string) {
    if (!obj || typeof obj !== 'object') return
    if (Array.isArray(obj)) {
      obj.forEach((item, i) => walk(item, `${path}[${i}]`))
      return
    }
    const record = obj as Record<string, unknown>
    if (typeof record.reference === 'string') {
      refs.push({ reference: record.reference, field: path })
    }
    for (const key of Object.keys(record)) {
      walk(record[key], `${path}.${key}`)
    }
  }

  walk(resource, resource.resourceType)
  return refs
}

This importer handles the recursive structure of FHIR resources and creates typed edges for every reference. In production, you extend this with resource-type-specific edge labels (HAS_CONDITION, HAD_ENCOUNTER, ORDERED_BY) derived from the reference context rather than a generic REFERENCES label — the specificity makes queries dramatically more readable and efficient.

The Clinical Patterns You Can Only See in a Graph

The real value of the FHIR graph architecture is not the storage model — it is what becomes queryable.

Care gap identification: Compare a patient's graph against a care guideline encoded as a graph pattern. A diabetic patient with active diabetes, no HbA1c in 90 days, and no upcoming lab order has a care gap that resolves to: (Patient)-[:HAS_CONDITION]->({code:'44054006'}) and NOT (Patient)-[:HAD_OBSERVATION {loincCode:'4548-4'}]->({date: recent}). Express the guideline as a graph pattern, run it against the patient population.

Cohort similarity: Find patients with similar graph signatures — same condition set, similar medication profile, similar demographic context — who responded well to a particular intervention. This is not exact matching; it is graph similarity at scale, and it produces genuinely useful treatment cohort suggestions.

Temporal pattern detection: Graph traversal can follow temporal edges — ordering observations by date, linking encounters in sequence, tracing medication changes over time. Lab result trends that predict complications become graph queries over time-ordered observation chains.

Provider network analysis: Which practitioners are co-treating the same high-risk patient cohort? Which referral patterns are associated with better outcomes for specific condition sets? These are graph topology questions that have no clean SQL equivalent.

Unexplained gaps: A patient with a recorded diagnosis but no associated encounter or observation is a data integrity problem — or a care gap. In a graph, this is a structural query: nodes with specific labels that lack expected edge connections.

SMART on FHIR + Graph as a Clinical AI Foundation

The architecture we built combines three layers:

  1. SMART on FHIR for authorized, standards-based access to patient data from the EHR
  2. Memgraph as the graph store — FHIR resources as nodes, references as edges, temporal ordering as edge properties
  3. LangGraph for orchestrating AI agents that traverse the graph to answer clinical questions

When a clinician asks "why is this patient's kidney function declining," the agent does not query a vector database for semantically similar text. It traverses the patient's graph: current medications with known nephrotoxic profiles, recent contrast imaging studies, chronic condition history, fluid intake observations, recent lab trends. It follows the edges that connect these nodes and surfaces a structured narrative derived from actual clinical relationships — not retrieved text chunks.

This is materially different from a RAG-over-documents approach. The graph encodes what the clinical record actually asserts, not what a language model infers from free text. The AI uses it to ask better questions, not to hallucinate answers.

The architecture is a living system. Each new patient population teaches it something. Each new EHR integration reveals edge cases in the data model. The graph is not a destination — it is infrastructure for a continuously improving clinical intelligence layer.

What This Means for Digital Health Builders

If you are building any of the following, the FHIR graph architecture is worth serious consideration:

  • Population health tools that need to query across large patient cohorts for specific clinical patterns
  • Care management platforms that need to identify gaps relative to clinical guidelines
  • Clinical AI assistants that need to reason about patient history rather than retrieve documents
  • Risk stratification tools that need to surface multi-factor patterns across conditions, medications, and utilization

The upfront investment in data modeling is higher than a relational approach. The query expressiveness at scale, and the quality of AI reasoning built on graph-structured clinical data, is significantly better.

We are just beginning to understand what is possible when you stop fighting the graph structure of clinical data and start building with it.


Building on FHIR and Want to Explore Graph Architecture?

This is one of the most technically differentiating capabilities we bring to digital health clients. 1Putt Health specializes in FHIR integration, SMART on FHIR application development, and advanced clinical data architecture — including graph-based approaches for population health and clinical AI. If you are building in this space and want to talk through your data architecture, book a discovery call.