How to Monitor Upload and Download Progress for Amplify Storage

When you can, tell a user why they are waiting and what they are waiting for, if they have to wait. It’s recommended to manage expectations with your UI. Why most apps don’t have this feature is because it is tricky to implement.

How do you know what is happening on the server that is making a fetch request take so long? Unless the server is updating the client via web socket then you are out of luck.

Setup a Frontend + Amplify Project

Any frontend set up should work but I’m most familiar with React so I’ll be showing you with a React project. But feel free to create a project in any framework; same principles apply.

npx create-react-app monitoring-storage-progress

To configure Amplify on this project, you need to have the Amplify CLI installed.

npm install -g @aws-amplify/cli

On the newly created project, run the init command to setup Amplify

amplify init

Since we intend to work with just Amplify Storage, run the add command to configure the project with AWS S3:

amplify add storage

Install the Amplify frontend library to your React project:

npm install aws-amplify

Configure the library in index.js before rendering the React tree:

import Amplify from 'aws-amplify';
import awsconfig from './aws-exports';
Amplify.configure(awsconfig);

Amplify adds an aws-exports file when you set up Amplify in case you were wondering.

Uploading Files

In your src/App.js add an input element of type file:

<div>
  <div>
    <label htmlFor="f">
      <input
        type="file"
        id="f"
        onChange={(e) => {
          uploadFile(e.target.files[0]);
        }}
      />
      <div>Pick a File to Upload</div>
    </label>
  </div>
</div>;

The onChange method calls uploadFile and passes it the file that you picked.

Before you implement uploadFile, import Storage from the Amplify library in the src/App.js file. You need the methods it exposes to upload and download files from AWS S3:

import { Storage } from "aws-amplify";

Now create the uploadFile function in the App component:

const [key, setKey] = React.useState("");

const uploadFile = async (file) => {
  try {
    const result = await Storage.put(file.name, file, {
      contentType: file.type,
    });
    console.log(result);
    setKey(result.key);
  } catch (error) {
    console.log(error);
  }
};

uploadFile forwards the file object to Storage.put which takes the name of the file, the file object and a configuration object. The file name does not have to be the name of the file that you are uploading, you can use any string.

After the upload, Storage.put returns a key which you can use to identify the file that was uploaded. Kinda like the unique ID for the uploaded file. I’ve set the returned key as the value of key state because we need the key to download the uploaded file.

Downloading Files

To download a file, call the Storage.get function and pass it a key to the file you want to download.

Add a button to trigger the download and call a downloadFile function in the onClick event handler:

{key && (
  <button
    onClick={() => {
      downloadFile();
    }}>
    Download
  </button>
)}

Add the downloadFile function in the App component:

const downloadFile = async () => {
  try {
    const data = await Storage.get(key, { download: true });
    console.log(data);
  } catch (error) {
    console.log(error);
  }
};

Monitoring Upload and Download Progress

The put and get methods on the Storage object take a config object as one of the arguments:

Storage.put(file.name, file, { /* Config */ });
Storage.get(key, { /* Config */ });

You can pass a function called progressCallback to the object and Amplify will call it with progress data:

const progressCallback = (progress) => {
  console.log(`Progress: ${progress.loaded}/${progress.total}`);
};

Storage.put(file.name, file, { progressCallback });
Storage.get(key, { progressCallback });

I took an extra step to convert the progress data to percentage values:

const progressCallback = (progress) => {
  const progressInPercentage = Math.round(
    (progress.loaded / progress.total) * 100
  );
  console.log(`Progress: ${progressInPercentage}%`);
};

const uploadFile = async (file) => {
  try {
    const result = await Storage.put(file.name, file, {
      contentType: file.type,
      // Progress callback
      progressCallback,
    });
    console.log(result);
    setKey(result.key);
  } catch (error) {
    console.log(error);
  }
};

const downloadFile = async () => {
  try {
    const data = await Storage.get(key, { download: true, progressCallback /*Progress Callback*/ });
    console.log(data);
  } catch (error) {
    console.log(error);
  }
};

Conclusion

You can take the demo even one step further and add a progress bar. Progress bars take a current value and a max value. And you have these data already. You can learn more tricks like this form the Storage docs

22