DEV Community

loading...
Cover image for React file upload using S3 pre-signed urls

React file upload using S3 pre-signed urls

Dan Stanhope
Updated on ・2 min read

Don't forget to like!

What are we building?

We're going to create a lambda function that generates a pre-signed url as well as a react front-end utilizing a really cool component library!

Traditionally, uploading files could be a bit of pain to implement & manage. Fortunately AWS allows you to upload objects directly to an S3 bucket using pre-signed urls. Pre-signed urls come with an expiration date, so you need to start your upload before then otherwise access gets blocked.

Walk through time.

The project is divided up into two sections, basically. The front-end and the back-end.

Head over to github to grab the code.

Back-end

We're going to be using CloudFormation and AWS SAM to create and deploy our Lambda function as well as create our S3 bucket. This function, when called, is going to generate our pre-signed url for us. You could just as easily host this code within your own API, too.

Firstly, make sure you've got aws-cli and aws-sam-cli installed and configured(setting up your keys & region etc.). Here's how to do this.

Once you're all set up and ready to go, all you need to do is run sam build followed by sam deploy --guided from inside the lambda function's root folder. SAM cli will guide you through the deployment and, once successful, you'll have a newly created S3 bucket and lambda function.

Make sure you copy your lambda function's api gateway url, as you'll need to make one small change in the Upload.js component.

Alt Text

Front-end

Update the Upload.js component with your API endpoint.

const SignedUploadDragAndDrop = () => {
  useRequestPreSend(async ({ items, options }) => {
    const files = items.length > 0 ? items[0] : {};

    let { file } = files;
    let { name, type } = file;
    let gateway = '<YOUR APIGATEWAY ENDPOINT URL>';

    const response = await axios(
      `${gateway}?` +
      new URLSearchParams({
        name,
        type,
      })
    );

   ....
Enter fullscreen mode Exit fullscreen mode

After this, just run yarn and yarn start from inside the frontend folder and you should end up with a page that looks like the one in this post's hero image.

I've used a seriously awesome component library called React-uploady for this tutorial. Specifically, I've combined its upload button, drag-and-drop and progress components. But there are several others you can add on. Check it out!

When you select a file to upload a request is made to retrieve the pre-signed url and, once returned, the upload begins. Pretty sweet.

Hope this helps!

Discussion (4)

Collapse
pbaker0804 profile image
pbaker0804

Great article! One question about the internals - do you know what needs tweaked to prevent the "WebKitFormBoundary" from being wrapped around every file? It's preventing downloads/opens inside of AWS.

An example text/plain, but all files are like this:

------WebKitFormBoundaryWfoHH5ngB2DmLUeM
Content-Disposition: form-data; name="file"; filename="test.txt"
Content-Type: text/plain
This is text.
------WebKitFormBoundaryWfoHH5ngB2DmLUeM--

I tried to tweak the return to no avail:

return {
  options: {
    destination: {
      url: uploadUrl,
      method: 'PUT',
      headers: {
        'Content-Type': type
      }
    },
  },
};
Enter fullscreen mode Exit fullscreen mode

Thank you!

Collapse
danstanhope profile image
Dan Stanhope Author • Edited

Hey there! Thanks very much. Glad you found it helpful.

Here’s a thread explaining the issue: github.com/aws/aws-sdk-js/issues/547

I believe the problem is we’re passing the entire form and not the file.

I’ll take a look tomorrow to make sure it works(not at my computer at the moment). Thanks for pointing this out!

Thanks again & best,
Dan

Collapse
pbaker0804 profile image
pbaker0804

Hi Dan! I sent an email to the author and he replied almost as fast as you :) You guys are great. He said add "sendWithFormData: false" to the options. It worked perfect, now it sends all file types to AWS without the wrapper, I can download it, view it, all the good stuff. Thanks so much!

New return:

return {
  options: {
    sendWithFormData: false,
    destination: {
      url: uploadUrl,
      method: 'PUT',
      headers: {
        'Content-Type': type
      },
    },
  },
Enter fullscreen mode Exit fullscreen mode
Thread Thread
danstanhope profile image
Dan Stanhope Author

That’s awesome! Great fix.

I’ll update the repo to include this new property.

Thanks for the help!