How to Monitor Upload and Download Progress for Amplify Storage
Tags: amplify, javascript, s3, react
Published: 2021-11-15 11:38:03

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.

[Amplify Storage](https://docs.amplify.aws/lib/storage/getting-started/) has a callback that keeps you updated on the upload and download progress of your file. Here's how to set it up.


## 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.

```sh
npx create-react-app monitoring-storage-progress
```

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

```sh
npm install -g @aws-amplify/cli
```

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

```sh
amplify init
```

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

```sh
amplify add storage
```
Install the Amplify frontend library to your React project:

```sh
npm install aws-amplify
```

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

```js
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`:

```js
<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:

```js
import { Storage } from "aws-amplify";
```

Now create the `uploadFile` function in the `App` component:

```js
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:

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

Add the `downloadFile` function in the `App` component:

```js
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:

```js
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:

```js
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:

```js
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](https://docs.amplify.aws/lib/storage/getting-started/q/platform/js/)