FHIR Batch Requests
FHIR allows users to create batch requests to bundle multiple API calls into a single HTTP request. Batch requests can improve speed and efficiency and can reduce HTTP traffic when working with many resources.
If you want to create a copy of a project, say for a new environment, this can be done using the $clone
operation rather than by creating a batch request. For more details see the Projects guide.
How to Perform a Batch Request
Batch requests are modeled using the Bundle
resource by setting Bundle.type
to "batch"
.
Batch requests are performed by sending a POST request to [baseURL]/
with a FHIR Bundle. The Medplum SDK provides the executeBatch
helper function to simplify this operation.
The details of your request will be in the entry
field of the Bundle
, which is an array of BundleEntry
objects. Each BundleEntry
should have the details of the resource you are working with, as well as additional information about the request you are making.
Element | Description |
---|---|
request.url | The URL to send your request to. This is relative to the base R4 FHIR URL (e.g. https://api.medplum.com/fhir/R4). |
request.method | The type of HTTP request you are making. Can be one of the following:
|
request.ifNoneExist | See below |
resource | The details of the FHIR resource that is being created/updated. |
fullUrl | See below |
Example: A simple batch request to simultaneously search for two patients
- Typescript
- CLI
- cURL
await medplum.executeBatch({
resourceType: 'Bundle',
type: 'batch',
entry: [
{
request: {
method: 'GET',
url: 'Patient/homer-simpson',
},
},
{
request: {
method: 'GET',
url: 'Patient/marge-simpson',
},
},
],
});
medplum post Bundle '
{
"resourceType": "Bundle",
"type": "batch",
"entry": [
{
"request": {
"method": "GET",
"url": "Patient/homer-simpson",
},
},
{
"request": {
"method": "GET",
"url": "Patient/marge-simpson",
},
},
],
}'
curl 'https://api.medplum.com/fhir/R4' \
-X POST
-H 'authorization: Bearer $ACCESS_TOKEN' \
-H 'content-type: application/fhir+json' \
-d '{
"resourceType": "Bundle",
"type": "batch",
"entry": [
{
"request": {
"method": "GET",
"url": "Patient/homer-simpson",
},
},
{
"request": {
"method": "GET",
"url": "Patient/marge-simpson",
},
},
],
}'
Example: Create multiple resources in one batch request
{
resourceType: 'Bundle',
type: 'batch',
entry: [
{
resource: {
resourceType: 'Patient',
identifier: [
{
system: 'https://example-org.com/patient-ids',
value: 'homer-simpson',
},
],
name: [
{
family: 'Simpson',
given: ['Homer', 'Jay'],
},
],
},
request: {
method: 'POST',
url: 'Patient',
},
},
{
resource: {
resourceType: 'Patient',
identifier: [
{
system: 'https://example-org.com/patient-ids',
value: 'marge-simpson',
},
],
name: [
{
family: 'Simpson',
given: ['Marge', 'Jacqueline'],
},
],
},
request: {
method: 'POST',
url: 'Patient',
},
},
],
};
Example: Make multiple calls to the _history endpoint in one batch request
{
resourceType: 'Bundle',
type: 'batch',
entry: [
{
request: {
method: 'GET',
url: 'Patient/homer-simpson/_history',
},
},
{
request: {
method: 'GET',
url: 'Patient/marge-simpson/_history',
},
},
{
request: {
method: 'GET',
url: 'Organization/_history',
},
},
],
};
Creating Internal References
A common workflow when using batch requests is creating a resource that references another resource that is being created in the same batch. For example, you may create a Patient
resource that is the subject
of an Encounter
created in the same batch request.
Creating internal references is done by assigning temporary ids to each bundle entry. The fullUrl
field is set to 'urn:uuid:'
followed by a temporary UUID.
Future bundle entries can refer to this resource using the temporary urn:uuid
.
Batches are processed in order, so resources must be created in your bundle prior to being referenced. To assist with this, you can use the reorderBundle helper function, which performs a topological sort to reorder bundle entries such that a resource is created before references to that resource appear in the bundle.
Example: Create a patient and encounter whose subject is the created patient
{
resourceType: 'Bundle',
type: 'transaction',
entry: [
{
fullUrl: 'urn:uuid:f7c8d72c-e02a-4baf-ba04-038c9f753a1c',
resource: {
resourceType: 'Patient',
name: [
{
prefix: ['Ms.'],
family: 'Doe',
given: ['Jane'],
},
],
gender: 'female',
birthDate: '1970-01-01',
},
request: {
method: 'POST',
url: 'Patient',
},
},
{
fullUrl: 'urn:uuid:7c988bc7-f811-4931-a166-7c1ac5b41a38',
resource: {
resourceType: 'Encounter',
status: 'finished',
class: { code: 'ambulatory' },
subject: {
reference: 'urn:uuid:f7c8d72c-e02a-4baf-ba04-038c9f753a1c',
display: 'Ms. Jane Doe',
},
type: [
{
coding: [
{
system: 'http://snomed.info/sct',
code: '162673000',
display: 'General examination of patient (procedure)',
},
],
},
],
},
request: {
method: 'POST',
url: 'Encounter',
},
},
],
};
Conditional Batch Actions
There may be situations where you would only like to create a a resource as part of a batch request if it does not already exist.
You can conditionally perform batch actions by adding the ifNoneExist
property to the request
element of your Bundle
.
The ifNoneExist
property uses search parameters to search existing resources and only performs the action if no match is found. Since you are already defining the url
to send the request to, you only need to enter the actual parameter in this field (i.e., everything that would come after the ?
when submitting an actual search).
Example: Create a patient and organization, only if the organization does not already exist
{
resourceType: 'Bundle',
type: 'transaction',
entry: [
{
fullUrl: 'urn:uuid:4aac5fb6-c2ff-4851-b3cf-d66d63a82a17',
resource: {
resourceType: 'Organization',
identifier: [
{
system: 'http://example-org.com/organizations',
value: 'example-organization',
},
],
name: 'Example Organization',
},
request: {
method: 'POST',
url: 'Organization',
ifNoneExist: 'identifier=https://example-org.com/organizations|example-organization',
},
},
{
fullUrl: 'urn:uuid:37b0dfaa-f320-444f-b658-01a04985b2ce',
resource: {
resourceType: 'Patient',
name: [
{
use: 'official',
family: 'Smith',
given: ['Alice'],
},
],
gender: 'female',
birthDate: '1974-12-15',
managingOrganization: {
reference: 'urn:uuid:4aac5fb6-c2ff-4851-b3cf-d66d63a82a17',
display: 'Example Organization',
},
},
request: {
method: 'POST',
url: 'Patient',
},
},
],
};
Performing Upserts
Previously, performing an "upsert" (i.e. either creating or updating a resource based on whether it already exists)
required using a batch operation. This functionality is now implemented directly as a conditional update
to provide strong transactional guarantees around the operation in a single, simple PUT
request.
Medplum Autobatching
The Medplum Client provides the option to automatically batch FHIR read
and search
requests using the autoBatchTime
parameter. This field allows you to set a time window during which to batch up any GET
requests. After this window expires, the MedplumClient
will add them to a Bundle
behind the scenes and then execute them as a batch request.
Autobatching works by creating a queue of Promises
issued within the autoBatchTime
window and then creating a bundle out of these requests. To allow the queue to be created, you must make sure that the main thread continues to run, so you should not use await
after each request. Using await
will pause the main thread each time a request is made, so a queue cannot be created.
Instead you should create the queue of Promise
requests and then use Promise.all()
to resolve all of them at once.
Details
Resolving Promises with autobatching
❌ WRONG// Main thread pauses and waits for Promise to resolve. This request cannot be added to a batch
await medplum.createResource({
resourceType: 'Patient',
name: [
{
family: 'Smith',
given: ['John'],
},
],
});
// Main thread pauses and waits for Promise to resolve. This request cannot be added to a batch
await medplum.createResource({
resourceType: 'Patient',
name: [
{
family: 'Simpson',
given: ['Homer', 'Jay'],
},
],
});
✅ CORRECT
const patientsToCreate = [];
// Main thread continues
patientsToCreate.push(
medplum.createResource({
resourceType: 'Patient',
name: [
{
family: 'Smith',
given: ['John'],
},
],
})
);
// Main thread continues
patientsToCreate.push(
medplum.createResource({
resourceType: 'Patient',
name: [
{
family: 'Simpson',
given: ['Homer', 'Jay'],
},
],
})
);
// Both promises are resolved simultaneously
await Promise.all(patientsToCreate);