Uploading files to AWS S3 client side | NodeJS and ReactJS/NextJS

How to upload files to AWS S3 directly from client side using NodeJS ReactJS and NextJS with sample code.


Uploading static files has become a huge part of web development over the years with social media, e-commerce, and dashboard-y sites. One of the huge problems with uploading static media is that we need to eventually store it in some more persistent place like a cloud storage platform like AWS S3, which is a common choice among developers.

So in this article, I will be covering how to upload static files to AWS S3 directly from the client-side application (ReactJS in my example). Now before we begin, let's be clear. This approach requires a backend server like NodeJS, you can also use something like AWS Lambda but I personally found it a little more tedious than creating a simple backend app in NodeJS, you can be creative and use NextJS API Routes as well.

To begin with:

Creating a pre-signed URL for our upload in NodeJS.

async function createPresignedUploadUrl(fileName: string, fileType: string) {
  const params = {
    Bucket: process.env.AWS_BUCKET_NAME,
    Key: fileName,
    Expires: 60,
    ContentType: fileType,
  const preSingedUrl = await s3.getSignedUrlPromise("putObject", params);
  return { preSingedUrl };

This would give you a pre-signed URL where you can upload your files.

Next step would be passing this to your frontend done most commonly using REST APIs and then you can send a XHR request to the URL using the code below as reference.

Frontend code | NextJS/ReactJS

const getPreSignedUrl = async (e: any) => {
  if (e.dataTransfer) {
    e.target.files = e.dataTransfer.files;
  const file = e.target.files[0];
  // Send Request to get the presigned URL
  // Your Logic
  // Call the uploadToS3 function
  uploadToS3(preSignedUrl, file);
const uploadToS3 = async (preSignedUrl: string, file: any) => {
  // XHR request with file progress
  const xhr = new XMLHttpRequest();
  xhr.open("PUT", preSignedUrl, true);
  xhr.setRequestHeader("Content-Type", file.type);
  xhr.upload.addEventListener("progress", (e) => {
    // You can add your progress bar animation code here.
  xhr.addEventListener("load", async () => {
    if (xhr.status === 200) {
      alert("file upload completed");
    } else {
      alert("Could not upload file.");
  xhr.addEventListener("error", () => {
    alert("Could not upload file.");
  xhr.addEventListener("abort", () => {
    alert("Upload aborted.");

This was a short article that I was always looking for this problem. Please support me by sharing this article with your friends and colleagues 😊