Upload Images to S3 buckets with React and Node in scalable way , using getSignedUrl

08/04/2023 Wassim Nassour

Hi folks, we will discuss how to upload images to S3 buckets using the getSignedUrl method. This method enables direct uploading of images to S3 without having to first upload them to your server.

How it’s work and why this way is more scalable:


This image explains the full flow.
Upload Images to S3 buckets with React and Node in scalable way , using getSignedUrl
One of the ways to upload images to S3 buckets is to handle the image on the server before sending it to S3. However, this requires more resources on the backend, which can result in slower processing times. An alternative approach is to upload the image with a signed URL. This method requires fewer resources because with getSignedUrl, we only need to specify the operation name and some parameter objects.
After using the getSignedUrl method to upload images to S3 buckets on the server, we are returned a URL as results. We can utilize the URL to perform a PUT request with the image on the React client. This approach enables direct uploading of the image to S3 without passing through the server. Consequently, the approach is more scalable, as it reduces the backend's load and improves processing times.

Security :

  • Users cannot spam our S3 bucket with many files.
  • Users cannot request
  • a URL for one file and then upload a different one.
  • URLs can expire.
  • URLs are generated securely through a request between our server and AWS.
  • URLs can only work for the specific S3 bucket they were created for.
  • implementations :

    this is the backend implementation
    const AWS = require('aws-sdk')
    
    const s3 = new AWS.S3()
    
    AWS.config.update({
      accessKeyId: process.env.AWS_ACCESS_KEY_ID,
      secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY
    })
    
    app.post('/upload-image', async (req, res) => {
      const { fileName, fileType } = req.query
    
      const s3Params = {
        // name_of_your_bucket_here
        Bucket: process.env.S3_BUCKET,
        // define name of the resource in the s3 bucket it’s better to use it like this
        // way resourseName +’/’+ random id better to use UID
        Key: fileName,
        // file type Image/Jpeg...
        ContentType: fileType,
        // expires in 60 seconds
        Expires: 60
      }
    
      try {
        const signedUrl = await getSignedUrl(s3, s3Params)
        console.log(signedUrl)
        res.json({ signedUrl })
      } catch (err) {
        console.error(err)
        next(err)
      }
    })
    

    React Implementation:

    Let's consider that you have created a form and are now in the function that will upload an image to your backend.
    async function uploadImage({ fileName, fileType }) {
      const options = { fileName, fileType }
    
      const response = await axios.post('/upload-image', options)
    
      if (response?.url) {
        axios.put(response?.url, file, options)
      }
    }
    

    Handling CORS

  • S3 Bucket setting
  • Create a DNS compliant bucket name
  • Set default encryption
  • Give appropriate read and write permissions
  • Add the following CORS rules
  •   <?xml version="1.0" encoding="UTF-8"?>
      <CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
        <CORSRule>
            <AllowedOrigin>/ origins allowed to upload image /</AllowedOrigin>
            <AllowedMethod>GET</AllowedMethod>
            <AllowedMethod>POST</AllowedMethod>
            <AllowedMethod>PUT</AllowedMethod>
            <AllowedMethod>HEAD</AllowedMethod>
            <AllowedMethod>DELETE</AllowedMethod>
            <MaxAgeSeconds>3000</MaxAgeSeconds>
            <AllowedHeader>*</AllowedHeader>
        </CORSRule>
      </CORSConfiguration>
    
    
    full code

    Created by @Wassim built with @NextJs deployed in @Vercel