Restful File Upload
# support
c
We are getting native errors in mobile when attempting to use the medplum SDK to createBinary or createAttachment. Do you have any examples or can you provide the endpoint for us to call to post the data to? This relates to us creating communications with attachments on the mobile UI. Digging into the client code it appears we can just mimic but want to make sure we have the endpoint correct. Happy to provide the errors we are getting form iOS and Android if that is also helpful for later debugging the SDK. I know we are pushing boundaries with react native. https://github.com/medplum/medplum/blob/a8267cc4/packages/core/src/client.ts#L1736
c
Hi @codejenn Bummer to hear that didn't work you out of the box. Yes, please send the error message. I filed a ticket for our team to investigate: https://github.com/medplum/medplum/issues/2963 The
createBinary
method posts the file contents to the
https://api.medplum.com/fhir/R4/Binary
endpoint The method is a pretty simple wrapper around our HTTP POST helper:
Copy code
createBinary(
    data: BinarySource,
    filename: string | undefined,
    contentType: string,
    onProgress?: (e: ProgressEvent) => void
  ): Promise<Binary> {
    const url = this.fhirUrl('Binary');
    if (filename) {
      url.searchParams.set('_filename', filename);
    }

    if (onProgress) {
      return this.uploadwithProgress(url, data, contentType, onProgress);
    } else {
      return this.post(url, data, contentType);
    }
  }
The
createAttachment
method is purely a convenience helper, which creates the
Binary
via HTTP call, and then creates an in-memory
Attachment
populated with the
Binary
url:
Copy code
async createAttachment(
    data: BinarySource,
    filename: string | undefined,
    contentType: string,
    onProgress?: (e: ProgressEvent) => void
  ): Promise<Attachment> {
    const binary = await this.createBinary(data, filename, contentType, onProgress);
    return {
      contentType,
      url: binary.url,
      title: filename,
    };
  }
Note that in FHIR, a
Binary
is an actual "resource type" (i.e., it lives in the database), whereas an
Attachment
is a JSON data type, and only exists inside other resources. For example, if I want to create a FHIR
Media
1. First create the
Binary
via API request 2. Create an
Attachment
in memory as part of the
Media
3. Create the
Media
via API request A simplified FHIR
Media
looks something like this:
Copy code
{
  "resourceType": "Media",
  "content": {
    "contentType": "image/jpeg",
    "url": "Binary/MY_BINARY_ID",
    "title": "picture.jpg"
  }
}
d
Hi Cody, thank you for this! I've just tried a binary upload with this Base64 data, using the
medplum.post
method that
createBinary
is wrapping. We received a successful response with an included URL, and we see the Binary as being created in our Medplum app, but under "Data" we are seeing "base64Binary not implemented" - any ideas?
r
When you mention you are seeing it - can you provide some context on where? Via the API? Or in a custom app? (There is a known issue on the Medplum App where browsing Binary files is misleading https://github.com/medplum/medplum/issues/1378). [Also, sorry for removing previous post, but was concerned that partial credentials were shown in the screen shot].
d
Hi Reshma! I am looking in our
app.medplum.com
project under Binary resources. Here are a couple of screenshots of what I'm seeing. It's probably worth noting that there is no URL listed here, but I did receive a URL in the
post
response
r
So Binary is a bit of a special resource. It's not really meant to be used on its own. Can you walk me through exactly what resource you are trying to create? Also the API (or SDK) calls that you used to create the resource? At a high level - are you trying to model (for example) a message with an image in it? If (for example) you used Cody's example above you should see the resource correctly in your Media page https://app.medplum.com/Media?_count=20&_fields=id,_lastUpdated&_offset=0&_sort=-_lastUpdated
d
We are attempting to create this Binary to attach an image to a
Communication
in our messaging application, here is the code we are using to create this resource:
Copy code
const uploadAttachment = async (attachment: Attachment) => {

        const url = MedplumBaseUrl + 'fhir/R4/binary';
        medplum?.setAccessToken(medplumToken);
        
        const attachmentUploadResponse = await medplum?.post(url, attachment.data, attachment.contentType ?? 'application/octet-stream');

        return attachmentUploadResponse;

    }
We would like to then use the
attachmentUploadResponse
values to build the
contentAttachment
of a
Communication
payload. In the above example, attachment.data represents the Base64 encoded string of the image itself
r
This needs better documentation, and we are working on a guide for it (https://github.com/medplum/medplum/issues/1307) - but I want to get you unblocked in the short term! There can be some nuances to handling Binary files across platforms, so I'm going to show how to create the resources via curl for clarity. First create the Binary and store it's ID like so curl 'https://api.medplum.com/fhir/R4/Binary' -X POST \ --header 'Content-Type: image/jpeg' \ --header 'Authorization: Bearer ' \ --data-binary "@my-image.jpg" The response will have a Binary id that you'll need to create the communication resource, using the Binary/ as the URL for the attachment like so: const communication = await medplum.createResource( { "resourceType": "Communication", "status": "completed", "subject": { "reference": "Patient/f7ea7dfc-a690-498b-b33f-1233", "display": "Test Patient" }, "sender": { "reference": "Practitioner/e7e4a752-4b57-4087-8659-123", "display": "Dr Alice" }, "payload": [ { "contentString": "Test comment" }, { "contentAttachment": { "contentType": "image/png", "url": "Binary/", "title": "my-image.png" } } ], } ); Once you read back the communication via the API for use in other applications the Binary/ will resolve into a signed temporary storage url you can use in an tag. Look at the "Communication" in your Medplum app - not the Binary to confirm that it is created correctly.
d
Hi Reshma, I'm not sure I'm receiving the Binary id back in the response. Here is the code I'm using for the request, along with the response I am receiving back:
Copy code
const uploadAttachment = async (attachment: Attachment) => {

        if (!attachment.data) return;
        
        const url = MedplumBaseUrl + 'fhir/R4/binary';
        const attachmentUploadResponse = await fetch(url, {
            method: 'POST',
            body: attachment.data,//this is the base64 string 
            headers: {
                'Content-Type':
                    attachment.contentType ?? 'application/octet-stream',

                Authorization: 'Basic ' + authHeader,
            },
        });

        return attachmentUploadResponse;

    }
Response:
Copy code
{
  "_bodyBlob": {
    "_data": {
      "__collector": [
        "Object"
      ],
      "blobId": "FEFFA356-2257-4B59-A6A6-627E82AAB8CD",
      "name": "binary.json",
      "offset": 0,
      "size": 715,
      "type": "application/json"
    }
  },
  "_bodyInit": {
    "_data": {
      "__collector": [
        "Object"
      ],
      "blobId": "FEFFA356-2257-4B59-A6A6-627E82AAB8CD",
      "name": "binary.json",
      "offset": 0,
      "size": 715,
      "type": "application/json"
    }
  },
  "bodyUsed": false,
  "headers": {
    "map": {
      "cache-control": "no-store, no-cache, must-revalidate",
      "content-length": "715",
      "content-security-policy": "default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none';",
      "content-type": "application/json; charset=utf-8",
      "date": "Wed, 04 Oct 2023 19:53:54 GMT",
      "pragma": "no-cache",
      "referrer-policy": "no-referrer",
      "strict-transport-security": "max-age=63072000; includeSubDomains; preload",
      "vary": "Accept-Encoding",
      "x-content-type-options": "nosniff",
      "x-frame-options": "DENY",
      "x-xss-protection": "1; mode=block"
    }
  },
  "ok": true,
  "status": 201,
  "statusText": "",
  "type": "default",
  "url": "https://api.medplum.com/fhir/R4/binary"
}
r
Could I ask you to try to create the Binary directly as follows (vs as attachment)? This post below will return a JSON response with the id. The reason I'm putting this example in as curl and not the SDK is that we can't make assumptions about the file system across all platforms and how to get the binary data. curl 'https://api.medplum.com/fhir/R4/Binary' -X POST \ --header 'Content-Type: image/jpeg' \ --header 'Authorization: Bearer ' \ --data-binary "@my-image.jpg"
d
I actually was not using the SDK in my example, just a javascript
fetch
and providing the binary data directly - I will perform the curl and report back
I was able to get a response back with a proper ID via the curl command - now working on trying to recreate in Javascript
Got it working!
r
Great!
d
I was able to create the communication with a contentAttachment in the format you provided earlier, with
url='Binary/<id>'
, though it appears there is still no image data being associated
r
Sent you a DM
d
We got it working! The team noticed what was ending up in the database was an ASCII encoded base64 string, that told them that I needed to run my base64 string through a a
Buffer.from('base64')
before adding it to the post body. Here is the final working solution in javascript:
Copy code
const uploadAttachment = async (attachment: Attachment) => {

        if (!attachment.data) return;

        const url = MedplumBaseUrl + 'fhir/R4/Binary';
        const base64Data = Buffer.from(attachment.data, 'base64')
        const attachmentUploadResponse = await medplum?.post(url, base64Data, attachment.contentType ?? 'application/octet-stream');
        return attachmentUploadResponse;

    }
r
Just to confirm - this is react native right?
d
Correct - this is in our react native project, being used to add attachments to chat communications
614 Views