Published on

How to Directly Upload File to AWS S3 from Client Side

Authors

One of the common use cases these days is to get files from your customers like for example letting your customer upload a profile picture which will be publically available.

For such cases, it's better to directly upload files to AWS S3 instead of uploading them to your server and then uploading it to AWS S3.

To directly upload files to AWS S3, we can follow the pre-signed URL approach.

Basically, with the help of AWS SDK, we'll be generating a signed URL with our configuration (like file size, the validity of the link, file name, etc) in our backend and sending that to the frontend. And from the frontend, directly upload the file to AWS S3.

Here's a snippet on how to do it using Node.js using their SDK

Dependency

We'll need to install SDK. You can install it using the following command

npm install @aws-sdk/s3-presigned-post

And we'll need to have access keys as well.

export AWS_REGION="us-east-1" # Your region here
export AWS_ACCESS_KEY_ID=
export AWS_SECRET_ACCESS_KEY=
export AWS_BUCKET_NAME="YOUR_BUCKET_NAME"

Generating signed URL

import { createPresignedPost } from "@aws-sdk/s3-presigned-post";

const getSignedUrl = async ({ fileName }) => {
  const bucketName = process.env.AWS_BUCKET_NAME;
  const Key = `public/${fileName}`;

  // https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/modules/_aws_sdk_s3_presigned_post.html

  const Conditions = [
    { acl: "public-read" },
    { bucket: bucketName },
    ["eq", "$acl", "public-read"],
    // 1048576 = 1MB
    // [("content-length-range", 0, 1048576 * 2)],
    ["content-length-range", 1, 1024 * 1024 * 2],
    ["starts-with", "$Content-Type", "image/"],
    // ["starts-with", "$key", "user/example/"]
  ];

  const Fields = {
    acl: "public-read",
  };

  const params = {
    Bucket: bucketName,
    Key,
    Conditions,
    Fields,
    // number of seconds for which the pre-signed policy should be valid
    Expires: 15, // 15min
  };

  const client = new S3Client({
    region: process.env.AWS_REGION,
    credentials: {
      accessKeyId: process.env.AWS_ACCESS_KEY_ID,
      secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
    },
  });

  try {
    const { url, fields } = await createPresignedPost(client, params);
    return { uploadUrl: url, fields };
  } catch (error) {
    console.log(error);
  }
};

Upload file to AWS S3 on client side

I've used axios for convenience, but you use fetch as well.

npm install axios

Helper file for file upload:

// file-upload.js
import axios from "axios";

export const uploadToS3 = async ({
  uploadUrl,
  file,
  fields = {},
  onProgressChange = () => {},
}) => {
  const formData = new FormData();

  Object.keys(fields).forEach((key) => {
    formData.append(key, fields[key]);
  });
  formData.append("Content-Type", file.type);

  //  Make sure to pass data first otherwise it'll throw an error like
  // Bucket POST must contain a field named 'key'.  If it is specified, please check the order of the fields.
  formData.append("file", file, file?.name);

  const parseProgress = (progressEvent) => {
    const progressPercentage =
      (progressEvent.loaded / progressEvent.total) * 100;
    onProgressChange(progressPercentage);
  };

  try {
    const res = await axios.post(uploadUrl, formData, {
      onUploadProgress: parseProgress,
    });

    return res.data;
  } catch (error) {
    throw error;
  }
};

export default uploadToS3;

And to upload the file, you can call this on the client side directly like this

  // https://developer.mozilla.org/en-US/docs/Web/API/File
  const fileToUpload = null; // assign your file here instead of null
  await uploadToS3({ uploadUrl, file: fileToUpload, fields });

Happy direct uploads!