Skip to main content

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.

Cloning a project

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.

ElementDescription
request.urlThe URL to send your request to. This is relative to the base R4 FHIR URL (e.g. https://api.medplum.com/fhir/R4).
request.methodThe type of HTTP request you are making. Can be one of the following:
  • GET: Read a resource or perform a search
  • POST: Create a resource
  • PUT: Update a resource
  • DELETE: Delete a resource
request.ifNoneExistSee below
resourceThe details of the FHIR resource that is being created/updated.
fullUrlSee below
Example: A simple batch request to simultaneously search for two patients
await medplum.executeBatch({
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',
},
},
],
};
Batch vs Transaction

Medplum does not currently distinguish between 'batch' and 'transaction' type Bundle resources and does not provide atomic transactions. This is currently being worked on, and you can track progress on the issue here.

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.

Note

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: 'batch',
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: 'batch',
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: 'identifer=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

You can combine conditional creates and internal references to perform an "upsert" operation to either create a resource or update it in place.

The example below first uses a POST request to conditionally create create a patient, and then performs a PUT with the same patient data to perform an in-place update.

If the patient doesn't exist:

  • POST creates the Patient
  • PUT updates the Patient with the exact same data (no-op)

If the patient does exist:

  • POST doesn't create a new Patient (no-op)
  • PUT updates the Patient
Example: Create a patient and update it in the same batch request
  {
resourceType: 'Bundle',
type: 'batch',
entry: [
// Create the patient if it doesn't exist
{
request: {
method: 'POST',
url: 'Patient',
ifNoneExist: 'identifier=http://example-hospital.org/mrns|234543',
},
fullUrl: 'urn:uuid:ffcda8f9-e517-412f-afde-5488cd176f68',
resource: {
resourceType: 'Patient',
identifier: [
{
system: 'http://example-hospital.org/mrns',
value: '234543',
},
],
name: [
{
family: 'Simpson',
given: ['Homer', 'Jay'],
},
],
gender: 'male',
birthDate: '1956-05-12',
},
},
// Update the patient in-place
{
request: {
method: 'PUT',
url: 'urn:uuid:ffcda8f9-e517-412f-afde-5488cd176f68',
},
resource: {
resourceType: 'Patient',
id: 'urn:uuid:ffcda8f9-e517-412f-afde-5488cd176f68',
identifier: [
{
system: 'http://example-hospital.org/mrns',
value: '234543',
},
],
name: [
{
family: 'Simpson',
given: ['Homer', 'Jay'],
},
],
gender: 'male',
birthDate: '1956-05-12',
},
},
],
};

Medplum Autobatching

The Medplum Client provides the option to automatically batch HTTP requests using the autoBatchTime parameter. This field allows you to set a timer that delays executing any HTTP requests. During this delay, if any additional HTTP requests come through, Medplum 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);