AWS Amplify API, Blob Response Type(Binary Data) and Error Handling

July 03, 2019  4 minute read  

Learn how to use AWS Amplify API with Axios to query endpoints that return Blob response type but JSON error response type.

A Blob object is a file-like object of immutable, raw data such as image, audio, zip et cetera.

AWS Amplify With Axios

On AWS Amplify Documentation, you can find the following example usage of its API object.


let apiName = 'apiName'; // replace this with your api name.
let path = '/path'; //replace this with your API path 
let config = {
    body: {}, // replace this with attributes you need
    headers: {} // OPTIONAL
}

API.post(apiName, path, config)
  .then(response => {
    // Add your code here
  })
  .catch(error => {
    console.log(error.response)
  });

API Gateway URL

An AWS Api Gateway url is in this format:


https://abcde12345.execute-api.us-east-2.amazonaws.com/Prod

A particular path of your endpoint will have the path name appended to the end of the above url. For example, the above API url with pathcomputelooks like this:

    
https://abcde12345.execute-api.us-east-2.amazonaws.com/Prod/compute

JavaScript Example

The following is an example to get application/zip Blob data from server using Amplify. Specify your Blob object’s mime type using Accept header.


function apiCall(data) {
  const config = {
    responseType: 'blob',
    body: {
      'data': data
    },
    headers: {
      'Content-Type': 'application/json',
      'Accept': 'application/zip'
    }
  };

  return API.post('myApi', '/compute', config)
    .then(res => {
      console.log('response: ', res);
      const url = window.URL.createObjectURL(res);
      return Promise.resolve(url);
    })
    .catch(error => {
      console.log(error.response);
      return Promise.reject();
    });
}

TypeScript Example

The following is an example to request for image/png Blob data from server. Specify your Blob object mime type using Accept header.


private apiCall(data: Data): Promise<BlobObj> {
  const config = {
    responseType: 'blob',
    body: {
      'data': data
    },
    headers: {
      'Content-Type': 'application/json',
      'Accept': 'image/png'
    }
  };

  return API.post('myApi', '/compute', config)
    .then(res => {
      console.log('response: ', res);
      const url = window.URL.createObjectURL(res);
      return Promise.resolve(url);
    })
    .catch(error => {
      console.log(error.response);
      return Promise.reject();
    });
}

Error Handling

If you use Amplify API which uses Axios library to get blob response type, your error response will be a Blob object too. Your server might return a JSON object when there is an error but it would be converted into a blob File object.

To extract your error response, you need to use a FileReader object.

MDN FileReader documentation says:

The FileReader object lets web applications asynchronously read the contents of files (or raw data buffers) stored on the user’s computer, using File or Blob objects to specify the file or data to read.

When a FileReader object reads the error response, the error response is available via onloadend callback function’s parameter. Its value can be accessed via param.currentTarget['result']. Refer to examples below for more information.

You can use a FileReader object to read error response returned by your server. However, when there is 5XX error, err.data may not be a Blob object. Thus, there is a need to wrap fileReader.readAsText() in a try catch block.

Catch clause of an Amplify API Post function:


.catch(err => {
  console.log(err)
  let fr = new FileReader();

  // fr.onload = function(result) {
  //   console.log('onload: ', result)
  // };
  fr.onloadend = function(e) {
    const errRes = e.currentTarget['result'];
    // if your error response is in JSON
    const error = JSON.parse(errRes);
    if (error) {
      return Promise.reject(error);
    } 
  }
  if (err && err.data) {
    try {
      fr.readAsText(err.data);
    } catch (err) {
      console.error(err);
      return Promise.reject(err);
    }
  }
});

A complete Amplify API request with error handling for Blob response looks like this.


function apiCall(data) {
  const config = {
    responseType: 'blob',
    body: {
      'data': data
    },
    headers: {
      'Content-Type': 'application/json',
      'Accept': 'application/zip'
    }
  };

  return API.post('myApi', '/compute', config)
    .then(res => {
      console.log('response: ', res);
      const url = window.URL.createObjectURL(res);
      return Promise.resolve(url);
    })
    .catch(err => {
      console.error(err)
      let fr = new FileReader();

      // fr.onload = function(result) {
      //   console.log('onload: ', result)
      // };
      fr.onloadend = function(e) {
        const errRes = e.currentTarget['result'];
        const error = JSON.parse(errRes);
        if (error) {
          return Promise.reject(error);
        } 
      }
      if (err && err.data) {
        try {
          fr.readAsText(err.data);
        } catch (err) {
          console.error(err);
          return Promise.reject(err);
        }
      }
    });
}

Fetch and XHR

The reason for this not so straightforward way to handle requests for Blob data is due to the behaviour of Axios library that AWS Amplify is using.

Instead of using AWS Amplify library API object, you can definitely use Fetch or XHR to send a request on frontend. However, AWS Amplify library streamlines signing API requests with AWS Signature V4. Consider the tradeoff and choose the method that works best for you.

Further Reading

Check out How to Configure API Gateway to Return Binary Response article to learn how to configure API Gateway to return binary response.

Support Jun

Thank you for reading!    Support JunSupport Jun

Support Jun on Amazon US

Support Jun on Amazon Canada

If you are preparing for Software Engineer interviews, I suggest Elements of Programming Interviews in Java for algorithm practice. Good luck!

You can also support me by following me on Medium or Twitter.

Feel free to contact me if you have any questions.

Comments