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);