Managing FHIR resources using FHIR bundles

This page explains how to manage FHIR resources by executing FHIR bundles. The ExecuteBundle method implements the FHIR standard batch/transaction interaction.

FHIR bundles

A FHIR bundle contains an array of entries, each of which represents an operation, such as create, update, or delete, on a resource, such as an Observation or a Patient. See the detailed descriptions for the elements in the Bundle resource.

When a FHIR bundle is executed, the bundle type determines how the operations in the bundle are performed. Setting the bundle type to batch is useful for executing multiple independent requests. Setting the bundle type to transaction is useful for executing multiple requests that depend on one another. For example, if a transaction bundle includes creating a Patient and then an Observation for the Patient, if the Patient request fails then the Observation is not created.

If an operation fails when the bundle type is batch, the remaining operations in the bundle continue to execute. If an operation fails when the bundle type is transaction, no further operations are executed and the transaction is rolled back.

The Cloud Healthcare API checks permissions for each operation in the bundle. There are no specific permissions for executing a bundle.

Executing a bundle

To execute a FHIR bundle, use the projects.locations.datasets.fhirStores.fhir.executeBundle method.

In the following samples, BUNDLE.json is the path and filename to a JSON-encoded FHIR bundle. You can also include the bundle in the request body.

The following sample Bundle creates a Patient resource and deletes another Patient resource.

{
  "resourceType": "Bundle",
  "type": "transaction",
  "entry": [
    {
      "resource": {
        "resourceType": "Patient",
        "name": [
          {
            "family": "Smith",
            "given": [
              "Darcy"
            ]
          }
        ],
        "gender": "female",
        "address": [
          {
            "line": [
              "123 Main St."
            ],
            "city": "Anycity",
            "state": "CA",
            "postalCode": "12345"
          }
        ]
      },
      "request": {
        "method": "POST"
      }
    },
    {
      "request": {
        "method": "DELETE",
        "url": "Patient/1234567890"
      }
    }
  ]
}

The following samples show how to execute a bundle.

curl command

curl -X POST \
    -H "Content-Type: application/fhir+json; charset=utf-8" \
    -H "Authorization: Bearer "$(gcloud auth print-access-token) \
    --data @BUNDLE_FILE.json \
    "https://healthcare.googleapis.com/v1beta1/projects/PROJECT_ID/locations/REGION/datasets/DATASET_ID/fhirStores/FHIR_STORE_ID/fhir"

Regardless of the outcome of the individual operations, after executing a batch bundle, the server returns a 200 OK HTTP status code and a JSON-encoded representation of a Bundle resource of type batch-response. The Bundle resource contains one entry for each entry in the request with the outcome of processing the entry, which might be a mix of success and error results.

If a transaction bundle is successful, the server returns a 200 OK HTTP status code and a JSON-encoded representation of a Bundle resource of type transaction-response containing one entry for each entry in the request with the successful outcome of the operation.

If an error occurs while executing a transaction bundle, the response body does not contain a bundle. Instead, it contains a JSON-encoded OperationOutcome resource describing the reason for the error. Successful operations that were rolled back are not reported in the response.

The following sample bundle is the output from successfully executing the above example. The first entry indicates the success of the operation to create a Patient and includes the ID of the new resource. The second entry indicates the success of the delete operation.

200 OK
{
  "entry": [
    {
      "response": {
        "location": projects/PROJECT_ID/locations/REGION/datasets/DATASET_ID/fhirStores/FHIR_STORE_ID/fhir/RESOURCE/RESOURCE_ID,
        "status": "201 Created"
      }
    },
    {
      "response": {
        "status": "200 OK"
      }
    }
  ],
  "resourceType": "Bundle",
  "type": "transaction-response"
}

PowerShell

$cred = gcloud auth print-access-token
$headers = @{ Authorization = "Bearer $cred" }

Invoke-RestMethod `
  -Method Post `
  -Headers $headers `
  -ContentType: "application/fhir+json" `
  -InFile BUNDLE_FILE.json `
  -Uri "https://healthcare.googleapis.com/v1beta1/projects/PROJECT_ID/locations/REGION/datasets/DATASET_ID/fhirStores/FHIR_STORE_ID/fhir" | ConvertTo-Json

Regardless of the outcome of the individual operations, after executing a batch bundle, the server returns a 200 OK HTTP status code and a JSON-encoded representation of a Bundle resource of type batch-response. The Bundle resource contains one entry for each entry in the request with the outcome of processing the entry, which might be a mix of success and error results.

If a transaction bundle is successful, the server returns a 200 OK HTTP status code and a JSON-encoded representation of a Bundle resource of type transaction-response containing one entry for each entry in the request with the successful outcome of the operation.

If an error occurs while executing a transaction bundle, the response body does not contain a bundle. Instead, it contains a JSON-encoded OperationOutcome resource describing the reason for the error. Successful operations that were rolled back are not reported in the response.

The following sample bundle is the output from successfully executing the above example. The first entry indicates the success of the operation to create a Patient and includes the ID of the new resource. The second entry indicates the success of the delete operation.

{
    "entry":  [
                  {
                      "response":  "@{location=projects/PROJECT_ID/locations/REGION/datasets/DATASET_ID/fhirStores/FHIR_STORE_ID/fhir/RESOURCE/RESOURCE_ID; status=201 Created}"
                  },
                  {
                      "response":  "@{status=200 OK}"
                  }
              ],
    "resourceType":  "Bundle",
    "type":  "transaction-response"
}

Go

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"

	healthcare "google.golang.org/api/healthcare/v1beta1"
)

// fhirExecuteBundle executes an FHIR bundle.
func fhirExecuteBundle(w io.Writer, projectID, location, datasetID, fhirStoreID string) error {
	ctx := context.Background()

	healthcareService, err := healthcare.NewService(ctx)
	if err != nil {
		return fmt.Errorf("healthcare.NewService: %v", err)
	}

	fhirService := healthcareService.Projects.Locations.Datasets.FhirStores.Fhir

	payload := map[string]interface{}{
		"resourceType": "Bundle",
		"type":         "transaction",
		"entry": []map[string]interface{}{
			{
				"resource": map[string]interface{}{
					"resourceType": "Patient",
					"active":       true,
				},
			},
		},
	}
	jsonPayload, err := json.Marshal(payload)
	if err != nil {
		return fmt.Errorf("json.Encode: %v", err)
	}

	parent := fmt.Sprintf("projects/%s/locations/%s/datasets/%s/fhirStores/%s", projectID, location, datasetID, fhirStoreID)

	call := fhirService.ExecuteBundle(parent, bytes.NewReader(jsonPayload))
	call.Header().Set("Content-Type", "application/fhir+json;charset=utf-8")
	resp, err := call.Do()
	if err != nil {
		return fmt.Errorf("ExecuteBundle: %v", err)
	}
	defer resp.Body.Close()

	respBytes, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return fmt.Errorf("could not read response: %v", err)
	}

	if resp.StatusCode > 299 {
		return fmt.Errorf("Create: status %d %s: %s", resp.StatusCode, resp.Status, respBytes)
	}
	fmt.Fprintf(w, "%s", respBytes)

	return nil
}

Java


import com.google.api.client.googleapis.auth.oauth2.GoogleCredential;
import com.google.api.client.http.HttpHeaders;
import com.google.api.client.http.HttpRequestInitializer;
import com.google.api.client.http.javanet.NetHttpTransport;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.jackson2.JacksonFactory;
import com.google.api.services.healthcare.v1beta1.CloudHealthcare;
import com.google.api.services.healthcare.v1beta1.CloudHealthcareScopes;

import java.io.IOException;
import java.net.URISyntaxException;
import java.util.Collections;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.client.methods.RequestBuilder;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.HttpClients;

public class FhirStoreExecuteBundle {
  private static final String FHIR_NAME = "projects/%s/locations/%s/datasets/%s/dicomStores/%s";
  private static final JsonFactory JSON_FACTORY = new JacksonFactory();
  private static final NetHttpTransport HTTP_TRANSPORT = new NetHttpTransport();

  public static void fhirStoreExecuteBundle(String fhirStoreName, String data)
      throws IOException, URISyntaxException {
    // String fhirStoreName =
    //    String.format(
    //        FHIR_NAME, "your-project-id", "your-region-id", "your-dataset-id", "your-fhir-id");
    // String data = "[{\"op\": \"add\", \"path\": \"/active\", \"value\": true}]";

    // Initialize the client, which will be used to interact with the service.
    CloudHealthcare client = createClient();
    HttpClient httpClient = HttpClients.createDefault();
    String baseUri = String.format("%sv1beta1/%s/fhir", client.getRootUrl(), fhirStoreName);
    URIBuilder uriBuilder = new URIBuilder(baseUri)
        .setParameter("access_token", getAccessToken());
    StringEntity requestEntity = new StringEntity(data);

    HttpUriRequest request = RequestBuilder
        .post()
        .setUri(uriBuilder.build())
        .setEntity(requestEntity)
        .addHeader("Content-Type", "application/fhir+json")
        .addHeader("Accept-Charset", "utf-8")
        .addHeader("Accept", "application/fhir+json; charset=utf-8")
        .build();

    // Execute the request and process the results.
    HttpResponse response = httpClient.execute(request);
    HttpEntity responseEntity = response.getEntity();
    if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) {
      System.err.print(String.format(
          "Exception executing FHIR bundle: %s\n", response.getStatusLine().toString()));
      responseEntity.writeTo(System.err);
      throw new RuntimeException();
    }
    System.out.print("FHIR bundle executed: ");
    responseEntity.writeTo(System.out);
  }

  private static CloudHealthcare createClient() throws IOException {
    // Use Application Default Credentials (ADC) to authenticate the requests
    // For more information see https://cloud.google.com/docs/authentication/production
    GoogleCredential credential =
        GoogleCredential.getApplicationDefault(HTTP_TRANSPORT, JSON_FACTORY)
            .createScoped(Collections.singleton(CloudHealthcareScopes.CLOUD_PLATFORM));

    // Create a HttpRequestInitializer, which will provide a baseline configuration to all requests.
    HttpRequestInitializer requestInitializer =
        request -> {
          credential.initialize(request);
          request.setHeaders(new HttpHeaders().set("X-GFE-SSL", "yes"));
          request.setConnectTimeout(60000); // 1 minute connect timeout
          request.setReadTimeout(60000); // 1 minute read timeout
        };

    // Build the client for interacting with the service.
    return new CloudHealthcare.Builder(HTTP_TRANSPORT, JSON_FACTORY, requestInitializer)
        .setApplicationName("your-application-name")
        .build();
  }

  private static String getAccessToken() throws IOException {
    GoogleCredential credential =
        GoogleCredential.getApplicationDefault(HTTP_TRANSPORT, JSON_FACTORY)
            .createScoped(Collections.singleton(CloudHealthcareScopes.CLOUD_PLATFORM));
    credential.refreshToken();
    return credential.getAccessToken();
  }
}

Node.js

const executeBundle = async (
  token,
  projectId,
  cloudRegion,
  datasetId,
  fhirStoreId,
  bundleFile
) => {
  const fs = require('fs');
  // Token retrieved in callback
  // getToken(serviceAccountJson, function(cb) {...});
  // const cloudRegion = 'us-central1';
  // const projectId = 'adjective-noun-123';
  // const datasetId = 'my-dataset';
  // const fhirStoreId = 'my-fhir-store';
  // const bundleFile = 'bundle.json';
  const parentName = `${BASE_URL}/projects/${projectId}/locations/${cloudRegion}`;

  const resourcesPath = `${parentName}/datasets/${datasetId}/fhirStores/${fhirStoreId}/fhir`;

  const bundle = fs.readFileSync(bundleFile);

  const options = {
    url: resourcesPath,
    headers: {
      Authorization: `Bearer ${token}`,
      'Content-Type': 'application/fhir+json; charset=utf-8',
    },
    body: bundle,
    method: 'POST',
  };

  try {
    const results = await request(options);
    console.log('Executed Bundle\n');
    console.log(JSON.stringify(results, null, 2));
  } catch (err) {
    console.error(err);
  }
};

Python

def execute_bundle(
        service_account_json,
        base_url,
        project_id,
        cloud_region,
        dataset_id,
        fhir_store_id,
        bundle):
    """Executes the operations in the given bundle."""
    url = '{}/projects/{}/locations/{}'.format(base_url,
                                               project_id, cloud_region)

    resource_path = '{}/datasets/{}/fhirStores/{}/fhir'.format(
        url, dataset_id, fhir_store_id)

    # Make an authenticated API request
    session = get_session(service_account_json)

    headers = {
        'Content-Type': 'application/fhir+json;charset=utf-8'
    }

    response = session.post(resource_path, headers=headers, json=bundle)
    response.raise_for_status()

    resource = response.json()

    print(json.dumps(resource, indent=2))

    return resource

Was this page helpful? Let us know how we did:

Send feedback about...

Cloud Healthcare API