How to upload images to AWS S3 using React.JS and Node.JS express server

Raz Levy
6 min readDec 27, 2021

--

Image by Joonko

One of the more difficult things for me when I started messing with AWS was to learning all the different ways to upload a file to an S3 bucket, and then determine the best realize in my situation (uploading directly from the Frontend or whether doing it involving a Backend server).

As a Full Stack developer at Joonko, we had a few different places in our application where we wanted to let the user the ability to upload an image, and for this, we chose to do it by forwarding the image from the frontend (ReactJS) to the backend server (NodeJS) using Base64 format and upload it from there directly to S3.

How did we do it?

Since our API did not support file uploading, we chose to convert the image that the user uploaded on the frontend to a base64 string and to pass it to the server using the request body. The server received the request, decoded the passed base64 string, and uploaded the original resource to S3 using the popular aws-sdk npm library.

In this tutorial, I’ll guide you on how to convert an image object on a frontend project built with ReactJS to base64, pass it to your server using an HTTP request (POST), and how to create a service on your backend server built with NodeJS specifically for uploading images to S3.

Prerequisites:

  • NodeJS installed.
  • AWS CLI v2 installed and configured with your AWS account.
  • AWS S3 bucket with public access permissions.

~ Create React application ~

Alright, so first, we will need to create a new React application.
Get into your terminal and use to the following command to create a new application:

npx create-react-app upload_image_to_s3

Now, open the newly created project on your favorite IDE, and let’s add some functionality to enable the user the ability to choose an image from his local machine:

~ Add the ability to upload an image ~

In the src directory, add a new file called uploadImage.js, and add the following code into it:

import React from 'react';const UploadImages = () => {
return (
<input type="file" accept="image/*" />
)
}
export default UploadImages;

The code above simply implements an input element that accepts only image files.
Now, import this component in your App.js, and the following input element should be rendered in your application:

Choose image input rendered

Next, we need to create a function to handle the onChange event on the input we created above, and a function that will convert the selected file into base64:

import React from 'react';const UploadImages = () => {
const onSelectFile = async (event) => {
const file = event.target.files[0];
const convertedFile = await convertToBase64(file);

// Request will be sent from here in the future
}
const convertToBase64 = (file) => {
return new Promise(resolve => {
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = () => {
resolve(reader.result);
}
})
}
return (
<input type="file" accept="image/*" onChange={onSelectFile}/>
)
}
export default UploadImages;

convertToBase64 function will convert our file into base64 using a the readAsDataUrl function of FileReader.
onSelectFile function will handle the onChange event for the input element, note that I’ve added the function to the onChange property of the input element as well.

Next, we will use Axios to send an asynchronous POST request to the server (which is not ready yet) and send our just created base64 string in its body.

So, first, install Axios using the following command:

npm install --save axios

Next, let’s import Axios at the top of the file and modify the onSelectFile function to send the POST request:

const onSelectFile = async (event) => {
const file = event.target.files[0];
const convertedFile = await convertToBase64(file);
const s3URL = await axios.post(
'http://localhost:3001/upload',
{
image: convertedFile,
imageName: file.name
}
);
}

I’ve also defined the s3URL variable so in the future we will also be able to display the image on the frontend.

Everything is ready on the frontend side, now, let’s move to the backend side of our project.

~ Create a simple Express server ~

You can simply create an Express server or easier clone a boilerplate I made for the NodeJS-Express server using the following GitHub repository.

In this section, I’ll cover the option using my own boilerplate which I just mentioned.

Get into the cloned directory and enter the following command into your terminal to install the project’s dependencies:

npm install

Next, get into src/routes and rename example.js to upload.js .

Photo by Douglas Lopes on Unsplash

~ Create image upload service ~

In this section, we’re going to create a simple images upload to AWS S3.
First, we need to install the aws-sdk, enter the following command:

npm install --save aws-sdk

Next, add a new environment variable called IMAGES_BUCKET with the name of your S3 bucket, navigate to src/services and create a new file called imagesService.js and enter the following code into it:

const AWS = require('aws-sdk');
const BUCKET_NAME = process.env.IMAGES_BUCKET;
const s3 = new AWS.S3({});

/**
*
@description Uploads an image to S3
*
@param imageName Image name
*
@param base64Image Image body converted to base 64
*
@param type Image type
*
@return string S3 image URL or error accordingly
*/
async function upload(imageName, base64Image, type)
const params = {
Bucket: `${BUCKET_NAME}/images`,
Key: imageName,
Body: new Buffer.from(base64Image.replace(/^data:image\/\w+;base64,/, ""), 'base64'),
ContentType: type
};

let data;

try {
data = await promiseUpload(params);
} catch (err) {
console.error(err);

return "";
}

return data.Location;
}
/**
*
@description Promise an upload to S3
*
@param params S3 bucket params
*
@return data/err S3 response object
*/
function promiseUpload(params) {
return new Promise(function (resolve, reject) {
s3.upload(params, function (err, data) {
if (err) {
reject(err);
} else {
resolve(data);
}
})
;
});
}

module.exports = {upload};

There are 2 functions above, the first one is the handler that will receive the data from the route that we will build in the next section.
The upload function simple receives the imageName, the body converted to base64 and the file type (jpg/png/svg/etc..), and build an object called params that will be passed to the promiseUpload function which is responsible to upload the file to S3 using the received params.

Photo by Luca Bravo on Unsplash

~ Create upload route ~

Navigate to src/routes and rename example.js to upload.js , remove the router.getfunction and add the following code instead:

router.post('/images', validateImageType, validateImageExtension, validateImageObject, validate, async (req, res, next) =>
const base64Image = req.body.image;
const imageName = req.body.name;
const type = req.body.type;
let response;

try {
response = await imagesService.upload(imageName, base64Image);
} catch (err) {
console.error(`Error uploading image: ${err.message}`);
return next(new Error(`Error uploading image: ${imageName}`));
}

res.send({link: response});
})

The following route is a POST route that should receive the following body:

  • image: The converted to base64 string.
  • imageName: The image’s source name.
  • type: The image’s type (image/jpeg, image/xml+svg, etc..)

and should use the imagesService we built earlier to upload it to S3, and return the new s3 generated link to the user.

--

--

Raz Levy
Raz Levy

Written by Raz Levy

Senior Full Stack developer, expert in Vanilla JS, React JS, Node JS, TypeScript and Cloud.

Responses (1)

Write a response