Skip to main content

Binary Data

Managing, storing, and retrieving binary files is a fundamental requirement in modern healthcare applications. Medplum, an open-source, API-first healthcare technology platform, provides a structured approach to this. This article dives into how to upload binary files to Medplum using the FHIR Binary resource type, detailing the process and Medplum's specific optimizations.

Introduction to FHIR Binary

The FHIR (Fast Healthcare Interoperability Resources) standard introduces the Binary resource type. It's designed to hold any kind of data in a binary form, encompassing but not limited to, images, PDFs, audio, and even video. This allows a standardized means to store and reference raw content, simplifying integration between healthcare systems.

Creating a FHIR Binary

To upload binary files to Medplum, you'll first need to create a Binary resource.

Using raw HTTP via curl

The HTTP API to upload a Binary is straightforward:

curl -X POST \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: image/jpeg" \
--data-binary "@./yourfile.jpg" \
https://api.medplum.com/fhir/R4/Binary

This is a short snippet, but each line has significant meaning.

First, the Authorization header is required to authenticate the request. See the Client Credentials tutorial guide for how to obtain an access token.

Next, the Content-Type header is required to specify the MIME type of the file being uploaded. You can always use the generic application/octet-stream type if you don't know the exact type, but beware that this may cause issues when consuming the Binary in an application. It is always recommended to use the correct MIME type if possible. See Common MIME types.

To upload a file, use the --data-binary flag. The --data-binary flag is used to upload the raw binary data to avoid any encoding issues. Also note curl's special syntax for referencing a file by file name using the "@" character.

Using the Medplum SDK MedplumClient

Medplum provides a JavaScript/TypeScript SDK that makes it easy to upload binary files.

const medplum = new MedplumClient({
clientId: 'YOUR_CLIENT_ID',
clientSecret: 'YOUR_CLIENT_SECRET',
});

const binary = await medplum.createBinary(myFile, 'test.jpg', 'image/jpeg');
console.log(result.id);

The return value is the newly created resource, including the ID and meta.

The data parameter can be string | File | Blob | Uint8Array.

A File object often comes from a <input type="file"> element.

A Blob object often comes from a fetch() call.

A Uint8Array object often comes from a FileReader or ArrayBuffer.

Beware passing in a string! This will cause the string to be encoded as UTF-8, which may not be what you want. If you have base-64 encoded content, you must first decode it to one of the binary types first before passing it to createBinary().

In addition to medplum.createBinary(), the Medplum SDK also provides medplum.createAttachment() which is similar but also creates an Attachment object. Note that Attachment is not a resource type, it is merely an in memory object to make it easier to work with binary data.

Referencing a Binary in an Attachment

Once uploaded, the Binary resource can be referenced in various FHIR resources.

a. Patient Profile Picture - Patient.photo:

const photo = await medplum.createAttachment(myFile, 'test.jpg', 'image/jpeg');

const patient = await medplum.createResource('Patient', {
resourceType: 'Patient',
photo: [photo],
});

b. Message Attachment - Communication.payload.contentAttachment:

const document = await medplum.createAttachment(myFile, 'test.pdf', 'application/pdf');

const communication = await medplum.createResource('Communication', {
resourceType: 'Communication',
payload: {
contentAttachment: document,
},
});
Attachment Data

When creating an Attachment, we recommend that you do not use the data field to provide the full binary data string. This can make the attachment very large and can cause issues when making HTTP requests. See the Handling External Files guide for an example of how to upload and reference Binary resources.

Consuming a FHIR Binary in an Application

In a normal FHIR server, when reading a FHIR resource that references a Binary, you'd receive a URL to the Binary resource.

For example:

{
"resourceType": "Patient",
"photo": [
{
"contentType": "image/jpeg",
"url": "Binary/12345"
}
]
}

And then you would be responsible for downloading the Binary resource.

Unfortunately, this poses of challenges:

  1. Accessing content in a browser can be tricky. Elements like <img> or <video> can't easily set the Authorization header.
  2. When dealing with massive files, especially videos, downloading the whole file isn't optimal. Instead, HTTP range requests, which fetch specific byte ranges, are preferred.

Medplum's Solution: AWS CloudFront Presigned URLs:

Medplum bypasses these limitations by automatically rewriting FHIR Attachment URLs referencing Binary resources to short-lived presigned URLs.

Instead of a normal Binary/{id} URL, the Attachment URL will look like this:

{
"resourceType": "Patient",
"photo": [
{
"contentType": "image/jpeg",
"url": "https://storage.medplum.com/12345?token=..."
}
]
}

This powerful feature grants several benefits:

  • Short-lived Access: These URLs expire after 60 minutes, ensuring data remains secure.
  • No Need for Authorization Header: Presigned URLs inherently carry authorization, meaning no additional headers are required.
  • Compatibility with Media Tags: Works seamlessly with HTML tags like <img> and <video>.
  • Supports Range Requests: Perfect for streaming large media files.

Using this feature in the browser can potentially cause CORS issues. If you are displaying the [Binary](https://www.notion.so/docs/api/fhir/resources/binary) in <a> or <object> tags, you can use the URL as is.

However, if you want to allow users to download the Binary you will need to use the Medplum provided download helper function. This function takes a URL in the Binary/{id} format. This string is included in the presigned URL, so you will need to parse it to use the function.

Example: Download a Binary
const patient: Patient = {
resourceType: 'Patient',
photo: [
{
contentType: 'image/jpeg',
url: 'https://storage.medplum.com/binary/12345',
},
],
};

const medplum = new MedplumClient();

// A function to return the binary id
const binaryUrl = getBinaryId(patient.photo?.[0].url);
// Download the binary
await medplum.download(`Binary/${binaryUrl}`);

In essence, Medplum elevates the utility of the FHIR Binary resource type. By streamlining the upload and retrieval processes, Medplum provides developers with a straightforward, optimized, and user-friendly means to manage binary files in healthcare applications.

Deep Dive on Storage URLs

Medplum's special treatment of Attachment.url and Binary references is powerful, but it can be confusing to understand how it works.

Here are some additional implementation details that may be helpful.

Write Time

When you create or update a FHIR resource with an Attachment that references a Binary, Medplum will "normalize" the URL to Binary/{id} form.

Medplum server can detect and rewrite URLs in the following cases:

  1. Attachment.url is a relative URL, e.g., Binary/12345
  2. Attachment.url is a full URL, e.g., https://api.medplum.com/fhir/R4/Binary/12345
  3. Attachment.url is a full URL, e.g., https://storage.medplum.com/12345

In all cases, Medplum will rewrite the URL to Binary/{id} form.

Read Time

When you read a FHIR resource (read by ID, read history, or search) that references a Binary, Medplum will generate a unique presigned URL.

When using the Medplum cloud service, this feature is enabled by default.

When self-hosting, this feature requires configuring a signing key. The Medplum CDK construct includes this automatically.

Learn more about AWS CloudFront presigned URLs: Using signed URLs

Create Binary via external URL

For large files such as videos and images, it can be inconvenient to download contents to the client before uploading to Medplum. In these situations, you can create a Media resource with a url parameter pointing to the location of the content.

See the Client Credentials tutorial guide for how to obtain an access token