Referencing Resources in Transactions
When creating multiple resources by using a FHIR Bundle, you may need to reference a resource that hasn’t yet been created in the FHIR server.
That poses a challenge. How do you reference an ID that hasn’t been assigned?
This is a common problem when using FHIR. In this tutorial we will explore two approaches to solve this: using hardcoded IDs and using temporary references.
Approach 1: Hardcode an ID and use it with PUT
The first method is to assign an ID yourself when creating a resource. This is done by using a PUT request and specifying the chosen ID in the URL.
For example, here is a batch Bundle where we create a Patient with ID 123 and also create an Observation that references the patient.
{
"resourceType": "Bundle",
"type": "batch",
"entry": [
{
"request": {
"method": "PUT",
"url": "Patient/123"
},
"resource": {
"resourceType": "Patient",
"id": "123",
"name": [
{
"given": ["Sidharth"],
"family": "Ramesh"
}
],
"telecom": [
{
"system": "email",
"value": "sidharth@medblocks.com"
}
]
}
},
{
"resource": {
"resourceType": "Observation",
"status": "final",
"code": {
"coding": [
{
"system": "http://loinc.org",
"code": "4548-4",
"display": "Hemoglobin A1c/Hemoglobin.total in Blood"
}
]
},
"valueQuantity": {
"value": 6.3,
"unit": "%",
"system": "http://unitsofmeasure.org",
"code": "%"
},
"subject": {
"reference": "Patient/123"
}
},
"request": {
"method": "POST"
}
}
]
}
You use PUT to create 'Patient/123' and then reference it in an Observation inside the same batch. Later, another client also tries to create 'Patient/123'. What risk does your approach introduce?
This approach has the following drawbacks:
- Not all servers allow client-assigned IDs
- Many servers enforce rules about valid ID formats
- You might run into conflicts if the ID is already taken
- If one request fails, the bundle may execute inconsistently
Approach 2: [Recommended] Use a transaction bundle with temporary references
The preferred and safer method is to use a transaction bundle, and assign temporary references in the fullUrl field.
Here’s how it works:
- Assign each resource a unique and temporary
fullUrl(e.g.urn:uuid:patient-123) - Reference that
fullUrlin related resources (like Observations) - When the Bundle is processed, the server replaces temporary references with real IDs
Also, since we are using a transaction, all resources succeed or fail, which ensures consistency.
Here’s an example of a transaction bundle that uses fullUrl:
{
"resourceType": "Bundle",
"type": "transaction",
"entry": [
{
"fullUrl": "urn:uuid:patient-123"
"request": {
"method": "POST"
},
"resource": {
"resourceType": "Patient",
"name": [
{
"given": ["Sidharth"],
"family": "Ramesh"
}
],
"telecom": [
{
"system": "email",
"value": "sidharth@medblocks.com"
}
]
}
},
{
"resource": {
"resourceType": "Observation",
"status": "final",
"code": {
"coding": [
{
"system": "http://loinc.org",
"code": "4548-4",
"display": "Hemoglobin A1c/Hemoglobin.total in Blood"
}
]
},
"valueQuantity": {
"value": 6.3,
"unit": "%",
"system": "http://unitsofmeasure.org",
"code": "%"
},
"subject": {
"reference": "Patient/urn:uuid:patient-123"
}
},
"request": {
"method": "POST"
}
}
]
}
To submit the above bundle to the server, use the base URL,
POST https://fhir-bootcamp.medblocks.com/fhir
On analyzing the server response, you can see that a real ID is assigned to the Patient, and Observation.subject is updated to reference this new Patient ID.
Since we used a transaction, the resources are created atomically, meaning either all succeed or all fail.
A developer uses a batch bundle with hardcoded ID 'Patient/999' and an Observation referencing it. Both requests succeed. Later, the same ID 'Patient/999' is used in another bundle for a different person. What subtle risk now exists?
Summary
When creating related resources in a FHIR Bundle,
- You can hardcode IDs with
PUT, but this is fragile and sometimes not supported - The recommended approach is to use a
transactionBundle with temporary references specified viafullUrl
This ensures that resource creation happens atomically (all-or-nothing), server-assigned IDs are safely resolved, and referential integrity is preserved.
