Solution: Synthetic Data - Advanced REST
Learn FHIR for FREE! Enroll Now!

Exercise Solution: Create Synthetic Data for your FHIR Server

HAPI FHIR Server with Synthea Exercise

This exercise shows how to run a local HAPI FHIR server and populate it with synthetic patient data from Synthea.

Reference: Synthea repository and README synthetichealth/synthea

Prerequisites

  • Docker Desktop (with Docker Compose)
  • Java 11 or 17 (LTS recommended)
  • Git
  • curl

NOTE: If you are using the zip file containing the FHIR sample data, you skip steps 4 and 5 and go directly to step 6. From inside the extracted folder, you can run the commands to load the data into the HAPI FHIR server.

Build Your First Real Fhir App in 15 Days Copy

Build your first real FHIR app in 15 days

500+ builders have done it. Live kick-off, structured modules, Demo Day. $27.

Join the challenge

Setup Configuration Files

Step 1: Create Docker Compose Configuration

Create a docker-compose.yml file with the following content:

version: '3.7'

services:
  # HAPI FHIR server container
  fhir:
    container_name: fhir
    image: "hapiproject/hapi:v7.0.0"
    ports:
      - "8080:8080"  # Expose FHIR API on localhost:8080
    configs:
      - source: hapi
        target: /app/config/application.yaml  # Mount custom config
    depends_on:
      - db  # Wait for database to start first
      
  # PostgreSQL database for FHIR data persistence
  db:
    image: postgres:14
    restart: always
    environment:
      POSTGRES_PASSWORD: admin
      POSTGRES_USER: admin
      POSTGRES_DB: hapi
    volumes:
      - ./hapi.postgress.data:/var/lib/postgresql/data  # Persist data locally

configs:
  hapi:
     file: ./hapi.application.yaml  # Reference to HAPI config file

Step 2: Create HAPI Application Configuration

Create a hapi.application.yaml file in the same directory with the following content:

spring:
  datasource:
    # PostgreSQL connection details (must match docker-compose db service)
    url: 'jdbc:postgresql://db:5432/hapi'
    username: admin
    password: admin
    driverClassName: org.postgresql.Driver
  jpa:
    properties:
      # Use HAPI-specific PostgreSQL dialect for optimal performance
      hibernate.dialect: ca.uhn.fhir.jpa.model.dialect.HapiFhirPostgres94Dialect
      # Disable search indexing for faster startup (optional)
      hibernate.search.enabled: false

Running the Services

Step 3: Start HAPI FHIR Server and Database

From the project folder containing your configuration files:

# Start the containers in detached mode
docker compose up -d

# Wait 30-60 seconds for services to initialize, then verify the server is running
# This should return FHIR capability statement showing server is ready
curl -s http://localhost:8080/fhir/metadata | head -n 20

Optional: Test FHIR Server Connectivity

Verify the server can accept FHIR resources (should return a Patient resource with ID):

curl -sS -H 'Content-Type: application/fhir+json' -X POST \
  http://localhost:8080/fhir/Patient \
  --data-binary '{"resourceType":"Patient","name":[{"use":"official","family":"Test","given":["Upload"]}],"gender":"female","birthDate":"1980-01-01"}'

Generating Synthetic Data

Step 4: Clone and Build Synthea

Clone Synthea alongside this project and build it:

# Clone the Synthea repository
git clone https://github.com/synthetichealth/synthea.git
cd synthea

# Build Synthea (skip tests for faster build)
./gradlew build -x test

Step 5: Generate Synthetic Patient Data

Generate 100 synthetic patients as FHIR R4 transaction bundles:

# Generate 100 patients with FHIR R4 export enabled
./run_synthea \
  -p 100 \
  --exporter.fhir.export=true \
  --exporter.fhir.transaction_bundle=true \
  --exporter.fhir.upload=false

Note: Generated files will be under synthea/output/fhir/

Loading Data into HAPI FHIR

Step 6: Upload Organization and Practitioner

Synthea generates system-wide bundles that patient records reference. Upload these first to ensure all references resolve correctly:

# Navigate to the FHIR output directory
cd output/fhir

# Upload hospital/organization information first
# This contains Organization resources that patient encounters reference
for f in hospitalInformation*.json; do
  [ -e "$f" ] || continue 
  echo "Uploading $f"
  curl -sS -H 'Accept: application/fhir+json' \
       -H 'Content-Type: application/fhir+json;charset=utf-8' \
       -X POST http://localhost:8080/fhir \
       --data-binary "@$f" -o /tmp/resp.json -w "HTTP %{http_code}\n" | cat
  head -n 40 /tmp/resp.json  
done

# Upload practitioner information
# This contains Practitioner resources that patient encounters reference
for f in practitionerInformation*.json; do
  [ -e "$f" ] || continue  
  echo "Uploading $f"
  curl -sS -H 'Accept: application/fhir+json' \
       -H 'Content-Type: application/fhir+json;charset=utf-8' \
       -X POST http://localhost:8080/fhir \
       --data-binary "@$f" -o /tmp/resp.json -w "HTTP %{http_code}\n" | cat
  head -n 40 /tmp/resp.json 
done

Why this step is important: References to Organization and Practitioner resources are used in patient bundles. Loading them first ensures all references are valid when patient data is uploaded.

Step 7: Upload Patient Bundles

Upload all remaining transaction bundles, excluding the metadata files uploaded above:

# Navigate to the FHIR output directory (adjust path as needed)
cd output/fhir

# Upload all patient bundle files, skipping metadata files
for f in *.json; do
  case "$f" in
    hospitalInformation*|practitionerInformation*) continue;;
  esac
  
  echo "Uploading $f"

  code=$(curl -sS -o /tmp/resp.json -w "%{http_code}" \
    -H 'Accept: application/fhir+json' \
    -H 'Content-Type: application/fhir+json;charset=utf-8' \
    -X POST http://localhost:8080/fhir \
    --data-binary "@$f")
  
  echo "HTTP $code"
  

  if [ "$code" -ge 400 ]; then
    echo "Error response:"; head -n 120 /tmp/resp.json; break
  fi
done

Step 8: Verify the Data Import

# Check total number of patients imported
curl -s 'http://localhost:8080/fhir/Patient?_summary=count'

# Fetch a sample of patient records to verify data structure
curl -s 'http://localhost:8080/fhir/Patient?_count=5' | head -n 120

# Optional: Check other resource types
curl -s 'http://localhost:8080/fhir/Observation?_summary=count'
curl -s 'http://localhost:8080/fhir/Encounter?_summary=count'

Comments (0)

No comments yet. Be the first to comment!