FHIR Platform Suite — Setup & Deployment Guide
Complete guide to deploying the Client and Vendor platforms on GitHub Pages and running a live FHIR R4 order → result message flow between them.
What you're deploying
Five files. Two platforms. One shared messaging bridge — all running in the browser with no backend.
client.html
Clinician-facing order platform. Create ServiceRequests via form, JSON, or XML. View returned results and download files.
vendor.html
Lab/radiology-facing portal. Receive orders, build DiagnosticReport results, attach files, send back to client.
fhir-bridge.js
Shared messaging layer. BroadcastChannel for real-time tab-to-tab delivery. localStorage for persistence and file storage.
index.html
Landing page with bridge status, order/result counters, and links to both platforms. Your deployment's home page.
Repository layout
All five files go in the root of your repository. No subfolders needed.
src="fhir-bridge.js" (relative path). If you rename any file, update the references inside the HTML files too.
How messages flow between platforms
client.html
submitOrder()
fhir_orders
fhir_orders_channel
vendor.html
vendor.html
sendResult()
fhir_results
fhir_results_channel
client.html
| Mechanism | What it does | Latency |
|---|---|---|
| BroadcastChannel | Instant message delivery between same-browser tabs | < 100ms |
| localStorage poll | Fallback — tabs check for new data every 4 seconds | ≤ 4s |
| storage event | Cross-tab notification when localStorage changes | < 200ms |
1Create a GitHub Repository
You need a free GitHub account and a new public repository.
Sign in at github.com
Go to github.com. Create a free account if you don't have one.
Click the + icon → "New repository"
Top-right corner of any GitHub page. Or go directly to github.com/new.
Configure the repository
Fill in these fields exactly:
| Repository name | fhir-platform (or any name — it becomes part of your URL) |
| Visibility | ✅ Public — required for free GitHub Pages |
| Initialize repository | ❌ Leave unchecked — you'll upload files manually |
Click "Create repository"
You'll land on an empty repository page. Keep this tab open for Step 2.
2Upload Your Files
Option A — Web Upload (no Git needed, recommended)
Click "uploading an existing file"
On the empty repository page, look for the text "…or upload an existing file" and click it.
Drag and drop all 5 files at once
Drag these files from your Downloads folder into the upload box simultaneously:
Commit the files
Scroll down, add the message Initial release, and click Commit changes.
Option B — Git Command Line
# 1. Clone your empty repository git clone https://github.com/YOUR_USERNAME/fhir-platform.git cd fhir-platform # 2. Copy your 5 downloaded files into this folder, then: git add index.html client.html vendor.html fhir-bridge.js .nojekyll README.md git commit -m "Initial release — FHIR Platform Suite" git push origin main
3Enable GitHub Pages
Open repository Settings
In your repository, click the Settings tab in the top navigation bar (the gear icon).
Find Pages in the left sidebar
Scroll down in the left sidebar until you see Pages under the "Code and automation" section. Click it.
Configure the source
| Source | Deploy from a branch |
| Branch | main |
| Folder | / (root) |
Click Save.
Wait ~60 seconds, then refresh
GitHub will display a green box with your live URL:
https://YOUR_USERNAME.github.io/fhir-platform/
4Verify Deployment
Open your Pages URL. You should see:
Landing page loads at /
The landing page (index.html) shows both platform cards and green bridge status indicators.
Both platform links open
Clicking "Open Client Platform" loads /client.html and "Open Vendor Platform" loads /vendor.html — each in a new tab.
Storage stats update
The counters on the landing page (Orders: 0, Results: 0, Files: 0) are live. After sending a test order they'll increment.
Test the complete message flow
Do this in the same browser on the same device. Open both platform tabs first.
Open both tabs
From the landing page, click both platform cards. Each opens in a new tab. Keep both open — don't close them.
Send an order from Client
In the Client tab: fill in the order form (patient name, test, priority) → click Send Order. The form builds a FHIR ServiceRequest and submits it via the bridge.
Watch the order arrive in Vendor
Switch to the Vendor tab. Within 1–4 seconds the order appears at the top of the Inbox with the patient name, test, and priority — colour-coded by urgency. A toast notification pops up.
Accept and build a result
Click Accept on the order. Go to Report Builder. The patient and order details are pre-filled. Enter lab values (e.g. Troponin 2.84 ng/mL, flag HH), optionally attach a PDF or image, then click Send Result.
Result arrives in Client Observations
Switch back to the Client tab. Go to Observations. The DiagnosticReport card appears with a result table, abnormal flags highlighted in red, and download buttons for every attachment.
Check order status updated
In Client, go to Order List. The status badge for your order has changed from Sent → Accepted → Complete automatically.
Downloading files on both sides
| Button | Platform | What downloads | Format |
|---|---|---|---|
| ↓ FHIR JSON (Order List) | Client | ServiceRequest resource | .json · application/fhir+json |
| ↓ FHIR JSON (Observations) | Client | DiagnosticReport Bundle | .json · application/fhir+json |
| ↓ [filename] (Observations) | Client | Attached file from vendor | Original file type (PDF, JPEG, etc.) |
| ↓ FHIR (Sent Results) | Vendor | DiagnosticReport Bundle | .json · application/fhir+json |
| ↓ FHIR (Inbox) | Vendor | ServiceRequest received | .json · application/fhir+json |
| ↓ [ext badge] (Sent Results) | Vendor | Attached file by type | PDF / DICOM / JPEG / HL7 |
Blob + URL.createObjectURL() — they work completely offline and require no server. Files attached by the vendor (stored as base64 in localStorage) are decoded back to their original format on download.
Browser storage limits
| File type | Typical size | Base64 size | Notes |
|---|---|---|---|
| FHIR ServiceRequest | ~2 KB | ~3 KB | No limit in practice |
| FHIR DiagnosticReport Bundle | ~8 KB | ~11 KB | No limit in practice |
| PDF lab report | 200 KB – 1 MB | 270 KB – 1.3 MB | Fine for most reports |
| JPEG / PNG X-Ray | 500 KB – 2 MB | 670 KB – 2.7 MB | Works well |
| Single DICOM file | 500 KB – 4 MB | 670 KB – 5.3 MB | Max ~3–4 MB per file |
| CT/MRI series (300+ slices) | 100+ MB | — | ⚠️ Too large — use ImagingStudy URL reference instead |
Total localStorage across all browsers is typically 5–10 MB. The landing page shows live usage. Click Clear All Data to reset if you hit the limit during testing.
Connecting to a real FHIR server
The bridge's localStorage layer handles real-time tab-to-tab messaging. For durable storage on a real FHIR server, use the built-in REST client in each platform:
On the Client Platform
Go to Settings → Server Config. Enter your FHIR base URL and authentication. Use the Raw Editor to POST ServiceRequests directly to the server.
On the Vendor Platform
Go to Settings → Vendor Configuration. Configure the client endpoint URL. Use the FHIR Composer to POST DiagnosticReport bundles directly.
Recommended open FHIR servers for testing
| Server | URL | Auth |
|---|---|---|
| HAPI FHIR (public) | https://hapi.fhir.org/baseR4 | None — open access |
| SMART Health IT | https://r4.smarthealthit.org | None — open access |
| Azure Health Data Services | Your instance URL | Azure AD Bearer token |
| AWS HealthLake | Your endpoint URL | AWS Signature V4 |
Cross-device messaging
Option 1 — Use a real FHIR server (recommended)
Both platforms can POST/GET from the same HAPI FHIR instance. Client sends ServiceRequest → Vendor polls for new ServiceRequests → Vendor POSTs DiagnosticReport → Client polls for results.
Option 2 — Add a WebSocket relay
Replace the BroadcastChannel calls in fhir-bridge.js with WebSocket events. A free Node.js server on Railway or Render can act as a relay in under 50 lines.
Option 3 — FHIR Subscriptions (R4B / R5)
FHIR R4B and R5 define a Subscription resource that pushes notifications via webhook or WebSocket when new resources arrive. This is the production-grade path.
// Example: replace BroadcastChannel in fhir-bridge.js with WebSocket const ws = new WebSocket('wss://your-relay.onrender.com'); function submitOrder(fhirResource, attachmentMetas) { // ... existing localStorage code stays ... ws.send(JSON.stringify({ event: 'NEW_ORDER', envelope })); return envelope; } ws.onmessage = (e) => { const msg = JSON.parse(e.data); if (msg.event === 'NEW_RESULT') resultCallbacks.forEach(cb => cb(msg.envelope)); };
Common issues and fixes
| Symptom | Cause | Fix |
|---|---|---|
| Vendor Inbox doesn't receive order | Different browsers or different devices | Open both tabs in the same browser window on the same machine |
| Order arrives after 4 seconds, not instantly | BroadcastChannel not supported (Safari private mode) | Normal fallback — storage event poll kicks in. Use Chrome for instant delivery |
| "File too large" error on attachment | File exceeds localStorage capacity | Compress the file, or use a URL reference for DICOM series |
| localStorage full warning | Too many files stored across sessions | Click "Clear All Data" on the landing page, then reload both platform tabs |
| GitHub Pages returns 404 | Branch name or folder wrong, or Pages not saved | Check Settings → Pages. Branch must be main (not master), folder / |
| GitHub Pages shows raw HTML source | Missing .nojekyll file |
Upload the .nojekyll file to the repository root |
| fhir-bridge.js not found (404 in console) | File not uploaded or wrong folder | Confirm fhir-bridge.js is in the repository root, same level as index.html |
| CORS error in Raw Editor | FHIR server blocks browser requests | Use https://hapi.fhir.org/baseR4 which has open CORS, or configure your server's CORS headers |
| Observations page blank after result sent | Both platforms not in the same browser | Open both in the same browser. Reload Client — the poll will pick up results within 4 seconds |