Skip to content

How to upload a picture to S3 using Node and React

Why upload picture to S3?

When building an application, most of the time, we will want to give our users the ability to upload a profile picture, or for some features, we will need to allow the user to upload and show pictures. For example, at a point of sale in a retail store, we can accomplish this by using a bucket from S3 service on AWS

Getting started (Server)

First we need to create a new folder an initialize a node project

mkdir node-s3-upload-picture
cd node-s3-upload-picture
npm init -y

Installing npm modules

This is a list of all the packages needed and the reason why we will install them:

"express" (npm i dotenv): Simplifies the creation of RESTful APIs
using Node
"dotenv" (npm install express): Dotenv is a zero-dependency module that loads environment variables from a .env file into process.env
"cors" (npm install cors): CORS is a node.js package for providing a Connect/Express middleware that can be used to enable CORS with various options.
"@aws-sdk/client-s3" (npm i @aws-sdk/client-s3): AWS SDK for JavaScript S3 Client for Node.js, Browser and React Native

Initializing server

Now we will create our express server, first create an app.js file on the base of the project, this file will contain the initialization of our express app and a route that will receive an image encoded on base64, we will use this route to connect to our S3 bucket and upload the picture

require("dotenv").config();

const express = require("express");
const cors = require("cors");

const { S3Client, PutObjectCommand } = require("@aws-sdk/client-s3");

// Initialize express app
const app = express();

// User cors to allow incoming requests
app.use(cors());

// Converts incoming JSON data to objects
app.use(express.json({ limit: "5mb" }));

// Basic route to test that the server is working
app.get("/health", (req, res) => {
  res.status(200).send("Working");
});

app.post("/upload-picture", async (req, res, next) => {
  try {
    const { imageName, imageData } = req.body;

    // Initialize S3
    const s3Client = new S3Client({
      region: process.env.awsRegion, // Specify the AWS region from environment variables
      credentials: {
        accessKeyId: process.env.accessKeyId, // Access key ID from environment variables
        secretAccessKey: process.env.secretAccessKey, // Secret access key from environment variables
      },
    });

    // Check file format
    if (!imageData.includes("data:image")) {
      throw new Error("Image format not supported");
    }

    // Create buffer from file data
    const buf = Buffer.from(
      imageData.replace(/^data:image\/\w+;base64,/, ""),
      "base64",
    );

    // Uplaod data information
    const uploadData = {
      Key: imageName,
      Body: buf,
      Bucket: process.env.bucketName,
      ContentEncoding: "base64",
      ContentType: "image/jpeg",
      ResponseCacheControl: "no-cache",
    };

    // Upload file
    await s3Client.send(new PutObjectCommand(uploadData));

    // File will be available at https://{uploadData.Bucket}.s3.amazonaws.com/{uploadData.Key}

    res.status(200).send({ msg: "Success" });
  } catch (e) {
    console.log(e);
    res.status(500).send({ msg: `Server error: ${e}` });
  }
});

module.exports = app;

Create a new file called index.js on the base of the project. We will use the app from above to listen for new requests and create a Ngrok tunnel on app start you could also use env variables to only initialize Ngrok if the environment is dev or local

// Import app from "./app file
const app = require("./app");

// Port that will be uses to listen to requests
const port = process.env.PORT || 3015;

/**
 * Listen on port 3015
 */
app.listen(port, () => {
  console.log(`App listening on port ${port}!`);
});

Finally create a file called .env on the base of the project with the following variables

awsRegion=aws region of bucket
bucketName=name of the bucket
accessKeyId=acces key of IAM user with permission to write on S3
secretAccessKey=secret key of IAM user with permission to write on S3

Initialize your project using "node index.js" command

Getting started (Client)

Now we will create a new react app so we can upload our picture

npx create-react-app react-s3-upload-picture

We will modify the App.js file in order to add an input to upload a file to our back-end

import { useState } from "react";
import "./App.css";

function App() {
  const [imageData, setImageData] = useState("");

  const handleFileChange = (event) => {
    if (!event.target.files[0]) {
      return;
    }

    const fileSize = event.target.files[0].size / 1024 / 1024; // in MiB

    // Check file size
    if (fileSize > 2) {
      alert("File size must be 2mb of smaller");
      return;
    }

    const reader = new FileReader();

    reader.readAsDataURL(event.target.files[0]);

    reader.onload = async () => {
      // Transform file to base 64
      setImageData(reader.result);
    };
  };

  const uploadImage = async () => {
    // Upload file as base 64
    const response = await fetch("http://localhost:3015/upload-picture", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        imageData: imageData,
        imageName: "profile-picture",
      }),
    });

    if (response.ok) {
      alert("Picture uploaded!");
    }

    if (!response.ok) {
      alert("Error uploading picture");
    }
  };

  return (
    <div className="App">
      <label for="avatar">Choose a profile picture:</label>

      <input
        type="file"
        id="avatar"
        name="avatar"
        accept="image/png, image/jpeg"
        onChange={(event) => handleFileChange(event)}
      />

      <button onClick={() => uploadImage()} disabled={!imageData}>
        Upload image
      </button>
    </div>
  );
}

export default App;

Initialize your project using "npm run start" command

Now upload a new picture using your react app, after that you should be able to see your new file on the bucket, all files are saved using the same format " https://{uploadData.Bucket}.s3.amazonaws.com/{uploadData.Key}" you can use this to your advantage to display the images, you could also save this link on a database if needed, dont forget to make your bucket public so we can access the images directly with the link