Upload Media directly to S3 bucket from Client.
Tags: aws, javascript, tutorial
Published: 2019-12-27 16:02:45

Web is becoming media rich day by day.

Most of the webapps now provides functionality of uploading user generated content, this content could be an Image or Video.

Generally the cloud storages used for storing media are storage buckets like [S3 buckets](https://docs.aws.amazon.com/AmazonS3/latest/dev/Welcome.html)

**Now if we want to upload image to S3 bucket, there are 2 methods:**

**1. From Server -** media upload to S3 bucket will happen on server and it is costly process on large scale

**2. From Client -** we are about to see it in detail here, in this method media upload to S3 bucket happens from the client which saves server processing and cost

### Client Side Media Upload Algo

1. Server uses aws sdk to use S3 bucket methods
2. Server expose and API to get **Signed Url** from aws and send to client
3. Client hits the API to get the signed url from the server
4. Client post the file on the signed url using XHR request
5. Client will be able to track the progress of the upload and can take some action once the Upload is complete
6. **Enable CORS on S3 bucket setting**

---
# Now lets do some coding

## Server

1. Create simple Express Server
2. Expose an endpoint to get signed URL

```js
// server
// npm install aws-sdk

const express = require("express");
const app = express();
const port = 3000;

const AWS = require("aws-sdk");
const s3 = new AWS.S3({
  accessKeyId: "<aws_access_key_id>", // aws access id here
  secretAccessKey: "<aws_secret_access_key>", // aws secret access key here
  useAccelerateEndpoint: true
});
const params = {
  Bucket: "<Bucket Name>",
  Key: "<Put your key here>",
  Expires: 60*60, // expiry time
  ACL: "bucket-owner-full-control",
  ContentType: "image/jpeg" // this can be changed as per the file type
};

// api endpoint to get signed url
app.get("/get-signed-url", (req, res) => {
  const fileurls = [];
  s3.getSignedUrl("putObject", params, function(err, url) {
    if (err) {
      console.log("Error getting presigned url from AWS S3");
      res.json({
        success: false,
        message: "Pre-Signed URL error",
        urls: fileurls
      });
    } else {
      fileurls[0] = url;
      console.log("Presigned URL: ", fileurls[0]);
      res.json({
        success: true,
        message: "AWS SDK S3 Pre-signed urls generated successfully.",
        urls: fileurls
      });
    }
  });
});

app.listen(port, () => console.log(`Server listening on port ${port}!`));
```
---
### S3 Bucket setting

1. Create a DNS compliant bucket name
2. Set default encryption
3. Give appropriate read and write permissions
4. Get aws accelerated URL like `yourbucketname.s3-accelerate.amazonaws.com`
5. Add following CORS rules
```xml
<?xml version=”1.0" encoding=”UTF-8"?>
<CORSConfiguration>
<CORSRule>
 <AllowedOrigin>*</AllowedOrigin>
 <AllowedMethod>POST</AllowedMethod>
 <AllowedMethod>GET</AllowedMethod>
 <AllowedMethod>PUT</AllowedMethod>
 <AllowedMethod>DELETE</AllowedMethod>
 <AllowedMethod>HEAD</AllowedMethod>
 <AllowedHeader>*</AllowedHeader>
</CORSRule>
</CORSConfiguration>
```
---
## Client

1. Make API call to server, get signed URL
2. Post multipart formdata to signed URL
3. Track progress, make UI changes accordingly
```js
import axios from "axios";
const getSignedURL = () => {
  return new Promise((resolve, reject) => {
    axios
      .get("<server-base-url>/get-signed-url")
      .then(data => {
        resolve(data);
      })
      .catch(err => {
        reject(err);
      });
  });
};

const uploadMediaToS3 = () => {
  const config = {
    onUploadProgress: function(progressEvent) {
      var percentCompleted = Math.round(
        (progressEvent.loaded * 100) / progressEvent.total
      );
      console.log(percentCompleted);
    }
  };

  let fd = new FormData();
  fd.append("file", files[0]);

  getSignedURL().then(data => {
    axios
      .put(data.urls[0], fd, config)
      .then(res => console.log("Upload Completed", res))
      .catch(err => console.log("Upload Interrupted", err));
  });
};

// add event listener to a html element
const uploadButton = document.getElementById("uploadButton");
uploadButton.addEventListener("onclick", uploadMediaToS3);
```

## Advantages of using client side upload method

1. It reduces load on servers.
2. Client can show actual upload progress
3. Client can abort the upload
4. Client can handle upload stop, pause and resume functionalities
5. At AWS we can pipe lambdas to do processing on these images and make it public only after processing it.

*As per the use case server may want to maintain the state of uploads started by client*