Bearcam Companion: Amplify Storage
Tags: aws, amplify, s3, tech4good
Published: 2022-07-31 02:52:33

In the Bearcam Companion application I am developing, I have been linking images directly to [Explore.org Snapshots](https://explore.org/snapshots/brown-bear-salmon-cam-brooks-falls/). Now I want to store the images in S3. This will make it easier to work with Rekognition and will eventually enable me to create training sets for machine learning model development. This post describes my experience working with Amplify Storage.

## Add S3 resource

Adding an S3 bucket in the Amplify CLI is as simple as:

```
amplify add storage
```

The command will walk you through some options, for example, storage type and access permissions (see the [documentation](https://docs.amplify.aws/cli/storage/overview/) for details). You can also set up S3 Lambda triggers, but I plan to trigger my Lambdas in a different way (more on that in another post to come).

Once the setup is complete, I did an `amplify push` to update my cloud resources. Now I can see the storage in Amplify Studio _Storage_:

![Amplify Studio Storage](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hjr0df1axjdkw66g6wgw.png)

## Add S3 file object to Images

I already have an **Images** data model from before. Currently, the model has a _url_, a _date_ and a 1:many relationship with **Objects**. Now I want to reference an object in the new S3 bucket. I added an S3 file object in **Images**. I decided to keep _url_ for future reference. 

In Amplify Studio _Data modeling_, I added a new model, which I called **S3Object**. This model contains a _bucket_, _region_ and _key_.

![S3Object Model](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/iwwkvttdd6a8faakho94.png)

I then added _file_, of type **S3Object**, to the **Images** data model:

![Images Model](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/g471f5igpj5s0f377cy6.png)

## UI to upload an Image

Next, I modified the _Add Image_ interface I created last time. It should upload an image to S3, in addition to saving the relevant information in the **Images** model. _Add Image_ already has a form to input a _url_ and _date_ then save the data to **Images**. I added a file selector and image preview to my client code:

```javascript
<input type="file" align="center" onChange={uploadFromFile}></input>
<img src={uploadImage} width={200} alt="Selected file" />
```

The function `uploadFromFile` sets a couple state variables, one for the `uploadFile` and one for a URL to the file, `uploadImage`, to display in the `<img>` above:

```javascript
  async function uploadFromFile(e) {
    const file = e.target.files[0];
    setUploadFile(file);
    setUploadImage(URL.createObjectURL(file));
  }
```

Once the image is selected and the URL and date are entered, clicking the _Save_ button will fire `buttonOnClick()`. This function will upload the image to the S3 bucket, then create an entry in the **Images** data model. Writing to S3 is pretty easy with `Storage.put()` from `aws-amplify`:

```javascript
  async function buttonOnClick () {
    try { // Upload Image to S3
      const result = await Storage.put(uploadFile.name, uploadFile);
      console.log("Stored", uploadFile.name, "to", result);
    } catch (error) {
      console.log("Error uploading file: ", error);
    }

    try { // Create new entry in Images
      saveImage (textURLValue, textDateValue, uploadFile.name);
    } catch (error) {
      console.log("Error uploading file: ", error);
    }
  }
```

In the code above, creating the **Images** record is handled by `saveImage()`. This function uses `DataStore.save()` from `aws-amplify`:

```javascript
  async function saveImage (url, date, key) {
    await DataStore.save(
      new Images({
        url: url,
        date: date,
        file: {
          bucket: awsExports.aws_user_files_s3_bucket,
          region: awsExports.aws_user_files_s3_bucket_region,
          key: key
        }
      })
    );
  }
```

The `bucket` and `region` information come from constants added to awsExports by the `amplify add storage` command earlier.

## Adding and viewing images

After implementing everything above, I can add images through the Bearcam Companion app:

![Add Image UI](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kl8obwxak2du2b2e25ot.png)

I can view the **Images** data in the Content section of Amplify Studio:

![Content View of Images](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/y4itkw7t4dqd2hlhgs4w.png)

A detailed view of an entry shows the new S3Object information under _file_:

![Edit Image View](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/g6xqzatd1cl05o7k6voy.png)

## Conclusion

Amplify makes it very easy to add storage to your web application. I used the CLI to add an S3 bucket and updated my data models using Amplify Studio. I modified the Bearcam Companion React application to upload a file to S3 and save the metadata to a table in DynamoDB using `Storage` and `DataStore` from `aws-amplify`.

Next time I will write about triggering a Lambda function to run the Rekognition object detector whenever a new image is added to the Images table in DynamoDB. Follow along here and on Twitter ([bluevalhalla](https://twitter.com/bluevalhalla)).