FHIR sandbox guide

The sandbox is a small, local FHIR R4 server, written in plain JavaScript, loaded with synthetic patients and observations. It lives in practice-repo/fhir-sandbox/, and its server is practice-repo/fhir-sandbox/server.js. Its README is the source of truth for data and commands. This guide is the learner's walk-through.

It needs only Node.js, which you already have for the practice repository. There is no Docker, no Java, and nothing else to install.

Everything in the sandbox is synthetic. NHS numbers are from the 999 range reserved for testing. Never put real patient data into it, even briefly.

It is a teaching server, not a real one. It supports Patient, Observation, and StructureDefinition, and only the parts of the FHIR REST API the course uses.

1. Start and stop

From practice-repo/:

npm run fhir     # start the sandbox at http://localhost:8080/fhir

It prints its address and how many resources it loaded. It loads the training profile and the synthetic data every time it starts, and keeps everything in memory, so a restart puts the data back as it was. Stop it with Ctrl+C.

You do not need to start it before running the API tests: if FHIR_BASE_URL is not set, the tests start it for you.

2. Look around by hand first

Before writing tests, explore the API the way a manual tester would. Start the sandbox, then use a browser or curl:

Request What you should see
GET http://localhost:8080/fhir/metadata The server's capability statement, listing what it supports
GET http://localhost:8080/fhir/Patient A searchset bundle of synthetic patients
GET http://localhost:8080/fhir/Patient/synthetic-adult One patient
GET http://localhost:8080/fhir/Patient?identifier=https://fhir.nhs.uk/Id/nhs-number|9990010005 Search by NHS number
GET http://localhost:8080/fhir/Observation?patient=synthetic-adult The adult's observations
GET http://localhost:8080/fhir/Observation?code=http://loinc.org|8867-4 Heart rate observations
GET http://localhost:8080/fhir/Patient?colour=blue 400, with an OperationOutcome listing the supported search parameters

Write down what each returns. This is the manual test you are about to automate.

The sandbox supports:

Interaction Request
Read GET /fhir/{type}/{id}
Create POST /fhir/{type}: 201 with a Location header
Update PUT /fhir/{type}/{id}: each update adds 1 to meta.versionId
Delete DELETE /fhir/{type}/{id}
Search GET /fhir/{type}?param=value. Patient: _id, identifier, family, given, name, gender, birthdate. Observation: _id, subject, patient, code, status. Any other parameter returns 400
Validate POST /fhir/{type}/$validate, optionally with ?profile=
Transaction or batch POST /fhir with a Bundle of type transaction or batch

3. Run the example suite

From practice-repo/:

npm run test:api

The suite is practice-repo/tests/api/fhir.test.js: Mocha tests that call the sandbox with Node's built-in fetch. If the server cannot be reached, every test fails with a connection error. That is an environment problem, not a product defect: read the message, then see Troubleshooting.

The base URL comes from FHIR_BASE_URL, defaulting to http://localhost:8080/fhir. Set it only to point the tests at a different server, such as a shared team test server with synthetic data (Decision 4).

4. What the example suite covers

Group Tests
Patient life cycle Create returns 201 and an id; read returns the same patient; search by identifier; update creates version 2
Observation Create for a synthetic patient; read and update; search by patient and LOINC code
Clinical codes Each synthetic observation has the right LOINC code; body temperature has a UCUM Celsius unit
Profile validation A patient with identifier and birthDate conforms to the training profile; one without birthDate fails
Negative tests An invalid gender code is rejected; reading a missing patient returns 404

5. Write your own

Write your tests in a new file in practice-repo/tests/api/, for example my-fhir.test.js. Do not edit the example suite. Use the example's helpers as a model: a syntheticPatient() data builder with overrides, and checks on OperationOutcome issues.

A test looks like this:

import { strict as assert } from 'node:assert';

const BASE = process.env.FHIR_BASE_URL ?? 'http://localhost:8080/fhir';

describe('Patient read', () => {
  it('returns the synthetic adult', async () => {
    const response = await fetch(`${BASE}/Patient/synthetic-adult`, {
      headers: { Accept: 'application/fhir+json' },
    });
    assert.equal(response.status, 200);
    const patient = await response.json();
    assert.equal(patient.resourceType, 'Patient');
    assert.equal(patient.identifier[0].value, '9990010005');
  });
});

The Evidence 12 requirements, by track:

Requirement Band 4 test engineering, Band 5 quality assurance (5 tests, with support) Band 6 quality assurance, Band 6 test engineering, Band 7 test engineering (full suite)
Create, read, search, update Patient Pick from these All
Create, read, search, update Observation Pick from these All
Status codes and bodies checked Yes Yes
Validate against the training profile At least 1 At least 1
A clinical code's meaning checked At least 1 At least 1
A negative test At least 1 At least 1
A partner system simulator — Band 7 test engineering only

Every test must fail when the behaviour is wrong. Show it once, as in Module 11.

6. Band 7 test engineering: partner system simulator

Write a small simulator for a partner system that sends observations to your service, for example a stub HTTP server built with Node's node:http module that returns a canned FHIR Bundle, with one normal case and one malformed case. Use it to test how your code handles both, without depending on the real partner. The sandbox's own server.js is a worked example of a small Node.js HTTP server. Record in your suite's spec what the simulator does and does not cover.

7. Troubleshooting

Symptom Likely cause Fix
Every test fails with a connection error The sandbox could not start or be reached Run npm run fhir on its own and read its message; check FHIR_BASE_URL is not set to another server
404 for synthetic-adult Your own test deleted or changed it, or another server is on port 8080 Restart the sandbox: its data is reset on every start
Connection refused, or "address in use", on port 8080 Another program is using port 8080 Stop that program, or ask your mentor; see the sandbox README
400 on a search A search parameter the sandbox does not support Read the OperationOutcome: it lists the supported parameters
fetch is not defined An old Node.js version Use Node.js 24, as pinned in practice-repo/.nvmrc

View this document on GitHub materials/modules/module-12-api-integration-fhir/fhir-sandbox-guide.md