React file uploader tutorial banner with NodeJS and TypeScript

This blog covers how to implement the functionality of uploading multiple files in a web application using React, Node.js, and TypeScript with a file upload component. You’ll build a fully functional React file uploader with a progress bar that tracks real-time upload progress. We’ll also integrate a simple Node.js backend using Multer to handle server-side uploads efficiently. Whether you’re building a React multiple file upload feature or enhancing user experience with a file upload progress bar, this guide walks through each step in detail. If you’re working with a reactjs development company, this setup can easily be adapted into a larger production-grade application.

Prerequisites

1. Basic understanding of Node.js, React, TypeScript, and npm.
2. Node.js installed on your system (if you don’t have it, download it from
here).

Setting Up a React Project

First, create a new React TS with Vite project:

npm create vite@latest new-project-name --template reactCode language: JavaScript (javascript)

Follow the CLI prompts to complete the setup. This approach is especially helpful when you’re aiming to hire reactjs developers who can efficiently implement scalable frontend architectures using TypeScript and modern tooling.

Step 1: Install Required Dependencies

1. Navigate to the project directory.

cd new-project-nameCode language: JavaScript (javascript)

2. Install Dependencies

npm install

3. Run the Project

npm run dev

Note:  If you get a Sass-embedded error, then run this 

npm install -D sass-embedded

Step 2: Basic Example of Uploading Multiple Files 

File upload process showing real-time progress bar in React

1. Create a file-upload.tsx file inside the src folder.

src/file-upload.tsx

import React, { useRef, useState } from "react";
import "./file-upload.scss";


function FileUpload() {
 const [files, setFiles] = useState<File[]>([]);
 const [progress, setProgress] = useState<number>(0);
 const [uploading, setUploading] = useState(false);
 const [successMessage, setSuccessMessage] = useState<string | null>(null);
 const [errorMessage, setErrorMessage] = useState<string | null>(null);
 const [filesDetails, setFilesDetails] = useState<
    { fileName: string; dataURL: string }[]
  >([]);
 const [userName, setUserName] = useState("");
 const inputRef = useRef<HTMLInputElement>(null);


  const callGetFilesApi = async (username: string) => {
    try {
      const response = await fetch(
        `http://localhost:8080/api/files/upload/${username}`
      );
      if (response.ok) {
        const data = await response.json();
        setFilesDetails(data);
      } else {
        throw new Error("Failed to fetch images.");
      }
    } catch {
      setErrorMessage("Failed to fetch images.");
    }
  };


  const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    if (e.target.files) {
      setFiles(Array.from(e.target.files));
    }
  };


  const downloadImage = (dataUrl: string, fileName: string) => {
    const link = document.createElement("a");
    link.href = dataUrl;
    link.download = fileName;
    document.body.appendChild(link);
    link.click();
    document.body.removeChild(link);
  };


  const handleFileUpload = async () => {
    const formData = new FormData();
    formData.append("username", userName.trim());
    files.forEach((file) => formData.append("files", file));


    setUploading(true);
    setProgress(0);
    setSuccessMessage(null);
    setErrorMessage(null);


    try {
      await uploadFiles(formData, (percent) => {
        const displayedProgress = Math.min(percent, 98);
        setProgress(displayedProgress);
      });
      await new Promise((res) => setTimeout(res, 300));
      setProgress(100);
      await new Promise((res) => setTimeout(res, 500));


      // Show success message
      setSuccessMessage("Files uploaded successfully!");
      setTimeout(() => setSuccessMessage(null), 5000);
    } catch {
      setErrorMessage("Error uploading files. Please try again.");
      setTimeout(() => setErrorMessage(null), 5000);
    } finally {
      setUploading(false);
    }
  };


  const uploadFiles = (
    formData: FormData,
    onProgress: (percent: number) => void
  ): Promise<void> => {
    return new Promise<void>((resolve, reject) => {
      const xhr = new XMLHttpRequest();


      xhr.upload.onprogress = (event) => {
        if (event.lengthComputable) {
          const percent = (event.loaded / event.total) * 100;
          onProgress(percent);
        }
      };


      xhr.onload = () => {
        if (xhr.status === 200) {
          const responseJson = JSON.parse(xhr.responseText);
          const user = responseJson.data[0];
          inputRef.current && (inputRef.current.value = "");
          setFiles([]);
          setUserName("");
          callGetFilesApi(user);
          resolve();
        } else {
          reject("Upload failed");
        }
      };


      xhr.onerror = () => reject("Upload failed");


      xhr.open("POST", "http://localhost:8080/api/files/upload", true);
      xhr.send(formData);
    });
  };


  return (
    <div className="center-div">
      <div className="file-upload-container" style={{ width: "300px" }}>
        <h1 className="file-upload-heading">Upload Files</h1>
        <input
          type="text"
          className="user-input"
          name="userName"
          placeholder="User Name"
          value={userName}
          onChange={(e) => setUserName(e.target.value)}
        />
        <input
          type="file"
          multiple
          ref={inputRef}
          onChange={handleFileChange}
          className="file-upload-input"
        />
        <button
          onClick={handleFileUpload}
          className="file-upload-button"
          disabled={!userName || files.length === 0}
        >
          {uploading ? "Uploading..." : "Upload"}
        </button>


        {uploading && (
          <>
            <div className="progress-container">
              <div
                className="progress-bar"
                style={{ width: '${progress}%' }}
              ></div>
            </div>
            <p className="progress-text">{Math.round(progress)}%</p>
          </>
        )}


        {successMessage && <p className="success-message">{successMessage}</p>}
        {errorMessage && <p className="error-message">{errorMessage}</p>}
      </div>


      {filesDetails.length > 0 && (
        <div className="div" style={{ width: "60%" }}>
          <div className="img-div" style={{ padding: "10px" }}>
            <div className="images">
              {filesDetails.map((file, index) => {
                const extension =
                  file.fileName.split(".").pop()?.toLowerCase() || "";
                const isImage = ["jpg", "jpeg", "png", "gif", "webp"].includes(
                  extension
                );


                const getFileIcon = (ext: string): string => {
                  const icons: { [key: string]: string } = {
                    pdf: "",
                    doc: "",
                    docx: "",
                    xls: "",
                    csv: "",
                    xlsx: "",
                    txt: "",
                    zip: "",
                    rar: "",
                  };
                  return icons[ext] || "";
                };


                return (
                  <div key={index} className="img-container">
                    {isImage ? (
                      <img
                        src={file.dataURL}
                        className="image"
                        height={50}
                        width={50}
                        alt={file.fileName}
                      />
                    ) : (
                      <div
                        className="file-icon"
                        style={{
                          height: "100px",
                          width: "100px",
                          fontSize: "60px",
                          display: "flex",
                          alignItems: "center",
                          justifyContent: "center",
                          backgroundColor: "#eee",
                          borderRadius: 4,
                        }}
                      >
                        {getFileIcon(extension)}
                      </div>
                    )}
                    <button
                      className="download-button"
                      onClick={() => downloadImage(file.dataURL, file.fileName)}
                    >
                      Download
                    </button>
                  </div>
                );
              })}
            </div>
          </div>
        </div>
      )}
    </div>
  );
}


export default FileUpload;Code language: PHP (php)

If you’re exploring practical uses of TypeScript in React, check out this detailed guide on Learn TypeScript with React by Building a CRUD Application.

Explanation:

1. State Variables:
  • files: Stores selected files for the upload using the file upload react component.
  • Progress: Tracks the upload progress bar as a percentage (0–100).
  • uploading: Boolean flag indicating if the file upload in React JS is in progress.
  • successMessage: Stores the success message to be displayed after a successful upload.
  • errorMessage: Stores the error message to be displayed if the upload fails.
  • filesDetails: Holds details of previously uploaded files (file name and data URL for images).
  • userName: Stores the username entered by the user.
  • inputRef: A reference to the file input element used to reset the input field after upload.
2. Fetching Previously Uploaded Files:
  • callGetFilesApi: Fetches the list of uploaded files from the server using the provided username. Updates the files state with the fetched data or shows an error if the request fails.
3. Handling File Selection:
  • handleFileChange: Triggered when the user selects files. It updates the files state with the selected files.
4. File Upload Process:
  • handleFileUpload: Handles the file upload process.
    • Creates a FormData object and appends the selected files and username.
    • Calls the uploadFiles function to upload the files.
    • Updates the progress state during the upload to show progress in percentage.
    • Displays a success message on successful upload and an error message if it fails.
5. Uploading Files with Progress:
  • uploadFiles: Uses XMLHttpRequest to upload the files to the server.
    • Tracks upload progress using the onprogress event and calls onProgress to update the progress.
    • Upon success, it clears the file input, resets the username, and fetches the list of uploaded files.
    • On failure, it rejects with an error message.
6. Rendering the Upload UI:
  • Displays:
    • A text input for the username.
    • A file input for file selection.
    • A button to trigger the upload, which shows “Uploading…” when in progress.
    • Success/Error messages based on the result of the upload.
    • A progress bar that shows the upload progress.
  • If files have been previously uploaded, they show them with a download button next to each file.
  • All file icons are visible except images.
7. Downloading Files:
  • downloadImage: A function that allows users to download the uploaded images. Triggers a download by simulating a click event.

Key Concepts:

  • React Hooks: Used for state management (useState) and managing references (useRef).
  • File Upload: Uses FormData to package files and XMLHttpRequest to handle the upload and progress.
  • Progress Bar: Updates as files are uploaded, providing visual feedback to the user.
  • Error and Success Handling: Displays messages based on the success or failure of the upload.
  • Downloading: Users can download images they’ve uploaded.

For more tutorials that reinforce concepts like this, refer to our Top 10 List of Best React Tutorials: Learn React JS for Free in 2022.

ReactJS development agency CTA for building scalable apps

2. Create a file-upload.scss file inside the src folder.

src/file-upload.scss

Please copy the below scss codes

/.file-upload-container {
 display: flex;
 flex-direction: column;
 padding: 20px;
 font-family: Arial, sans-serif;
}
.file-upload-heading {
 font-size: 2rem;
 margin-bottom: 20px;
 color: #333;
 text-align: center;
}
.file-upload-input {
 padding: 10px;
 font-size: 1rem;
 border-radius: 5px;
 border: 1px solid #ccc;
 margin-bottom: 20px;
 cursor: pointer;
}
.user-input {
 padding: 10px;
 font-size: 1rem;
 margin-bottom: 20px;
 border-radius: 5px;
 border: 1px solid #ccc;
 width: 276px;
 cursor: pointer;
}
.file-upload-button {
 padding: 10px 20px;
 background-color: #4caf50;
 color: white;
 border: none;
 border-radius: 5px;
 font-size: 1rem;
 cursor: pointer;
 transition: background-color 0.3s ease;
}
.file-upload-button:hover {
 background-color: #45a049;
}
.file-upload-button:disabled {
 background-color: #ccc; /* Light grey */
 color: #888; /* Dark grey text */
 cursor: not-allowed;
}
.center-div {
 display: flex;
 flex-direction: column;
 align-items: center !important;
 width: 100% !important;
 min-height: 85vh !important;
}
.file-list-container {
 width: 100%;
}
.file-item {
 margin-bottom: 20px;
 text-align: center;
 width: 100%;
}
.file-name {
 margin-bottom: 5px;
 font-size: 1.1rem;
 color: #333;
}
.progress-container {
 width: 100%;
 background-color: #f3f3f3;
 border-radius: 5px;
 margin-top: 10px;
}
.progress-bar {
 height: 10px;
 border-radius: 5px;
 background-color: #4caf50;
}
.progress-text {
 font-size: 1rem;
 color: #555;
}
.images {
 display: flex;
 gap: 20px;
 flex-wrap: wrap;
}
.img-container {
 position: relative;
 display: inline-block;
}
.image {
 width: 100px;
 height: 100px;
 border-radius: 8px;
 box-shadow: 2px 2px 10px rgba(0, 0, 0, 0.2);
}
.download-button {
 position: absolute;
 bottom: 2px;
 right: 0px;
 background: green;
 color: white;
 border: none;
 border-radius: 8px;
 padding: 0px;
 cursor: pointer;
 transition: background 0.3s;
 height: 22px;
 width: 100px;
 text-align: center;
}
.img-style {
 height: 22px !important;
 width: 20px !important;
}
.img-div {
 justify-content: center;
 align-items: center;
 background-color: #f8f9fa;
 padding: 10px !important;
}Code language: PHP (php)

3. Setup of App.tsx

Replace below code with src/App.tsx codes and import FileUpload.
import "./App.css";

import FileUpload from "./file-upload";

function App() {

  return <FileUpload />;

}

export default App;Code language: JavaScript (javascript)

Explanation: 

It is the root component of the React app, containing the main UI structure and application logic. All other components are nested within App.tsx to build the application’s layout and functionality. For the file react file upload component, you need to create code file-upload.tsx and scss file

In app.css just update root id css

#root {

text-align: center;

width: 100%;

}Code language: CSS (css)

4. Run the Project

npm run dev

Note:  If you get a Sass-embedded error, then run this 

npm install -D sass-embedded

Setting up the Node.js Project

File uploader integration with NodeJS backend in React

Let’s start by setting up the project environment using Node.js, Express and Multer.

These steps are commonly followed by teams at any professional nodeJS development company building robust server-side APIs for file handling.

Step 1: Initialize the Project

  • Create a new directory for your project:
    mkdir node-your-project
  • Move into the project directory:
    cd node-your-project
  • Initialize the node project:
    npm init -y

Step 2: Install Dependencies

  • Install the core dependencies for the Express framework
    npm install express
  • Install the typescript
    npm install typescript
  • TypeScript Configuration
    npx tsc --init
  • Install Multer
    npm install multer
  • Install cors
    npm install cors
  •  Install TypeScript and necessary type definitions
     npm install --save-dev typescript ts-node @types/express  @types/multer  @types/corsCode language: CSS (css)

Basic Example: Upload Multiple Files

Step 1: Set Up Basic Express Server 

1. Create src folder and create a app.ts file

Create the src/app.ts file and set up a basic file upload server using Express. 

 src/app.ts

import express from "express";
import cors from "cors";
const app = express();
app.use(cors());
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
export default app;Code language: JavaScript (javascript)

Explanation:

This code sets up an Express server with CORS support, JSON and URL-encoded data parsing, and routes file-related requests to a file-routes module. The server is then exported for use in other parts of the application.

2. Create a server.ts file

Create the src/server.ts and start the Express server on port 8080 and log a message to confirm it’s running.

 src/server.ts

import app from "./app";

const PORT = 8080;

app.listen(PORT, () => {

  console.log(`Server running on http://localhost:${PORT}`);

});Code language: JavaScript (javascript)

Explanation:

Please import the first app.ts  and This code starts the Express server and makes it accessible on the specified port. 

3. Run the Project  : 

npx ts-node src/server.ts

Step 2: Create Files Upload Post API

1. Create controller file in src/controller folder

src/controller/files-upload-controller.ts

import { NextFunction, Request, Response } from "express";

export const uploadFiles = async (req: Request, res: Response) => {

  try {

    const files = req.files as Express.Multer.File[];

    const uploadResult = await uploadFileService(files, req.body.username);

    res.status(200).json({

      message: "Files uploaded successfully",

      data: uploadResult,

    });

  } catch (error) {

    res.status(500).json({

      message: "File upload failed",

    });

  }

};Code language: JavaScript (javascript)

Explanation:

This controller handles file uploads, uses a service to process the files, and sends a success or failure response back to the client. And you will get import  error of service file , Then you need to create service file and required function and then import in controller

2. Create service file in src/service folder 

 src/service/file-services.ts

import path from "path";

import fs from "fs";

export const uploadFileService = (

  files: Express.Multer.File[],

  username: string

) => {

  return new Promise<string[]>((resolve, reject) => {

    try {

      const uploadDir = path.join(__dirname, `../../uploads/${username}`);

      if (!fs.existsSync(uploadDir)) {

        fs.mkdirSync(uploadDir, { recursive: true });

      }

      const filePaths = files.map((file) => {

        const filePath = path.join(uploadDir, file.originalname);

        fs.writeFileSync(filePath, file.buffer);

        return username;

      });

      resolve(filePaths);

    } catch (error) {

      reject(error);

    }

  });

};Code language: JavaScript (javascript)

Explanation:

This service function saves uploaded files to a user-specific directory using multer and returns the paths where the files were saved.

3. Create routes file in src folder 

src/file-routes.ts

import express from "express";

import multer from "multer";

import {

  uploadFiles,

} from "./controller/files-upload-controller";

const upload = multer({ storage: multer.memoryStorage() });

const router = express.Router();

router.post("/upload", upload.array("files"), uploadFiles);

export default router;Code language: JavaScript (javascript)

Explanation:

First import the uploadFiles controller function. and  this route handles file uploads to a specific folder. It uses the files-upload-controller for handling the logic and multer middleware for managing the file storage. 

API URL-    POST- http://localhost:8080/api/files/upload

Request – formData 

username: user
files: (binary)
files: (binary)

4. Add these route codes in app.ts file and please import fileRoutes

import fileRoutes from "./file-routes"
app.use("/api/files", fileRoutes);Code language: JavaScript (javascript)

5. Run the project 

npx ts-node src/server.ts

Step 3: Get Uploaded Files along with these API’s codes

Drag and drop file upload feature in React component

1. Create a getUploadedFiles function inside 

src/controller/files-upload-controller.ts

export const getUploadedFiles = async (

  req: Request,

  res: Response,

  next: NextFunction

) => {

  try {

    const images = await getUploadedFilesByUser(req.params.username);

    res.status(200).send(images);

  } catch (error) {

    return next(error);

  }

};Code language: JavaScript (javascript)

Explanation:

It’s causing service function errors, so you need to create a function within the service file. After creating the function, import it into the controller. This function will handle retrieving the images uploaded by the user and send them back in the response, while also managing any errors that may arise during the process.

2. Create a getUploadedFilesByUser function inside 

src/service/file-services.ts

export const getUploadedFilesByUser = (username: string) => {

  const uploadDir = path.join(__dirname, `../../uploads/${username}`);

  return getImageDataURLs(uploadDir);

};

const getImageDataURLs = async (uploadedDir: string) => {

  const files = fs.readdirSync(uploadedDir);

  const imageDataURLs: { fileName: string; dataURL: string }[] = [];

  files.forEach((file) => {

    const filePath = path.join(uploadedDir, file);

    const fileType = path.extname(file).substring(1);

    const imageBuffer = fs.readFileSync(filePath);

    const base64Data = imageBuffer.toString("base64");

    // Create Data URL

    const dataURL = `data:image/${fileType};base64,${base64Data}`;

    imageDataURLs.push({ fileName: file, dataURL });

  });

  return imageDataURLs;

};Code language: JavaScript (javascript)

Explanation:

This code provides functionality to retrieve user-uploaded images as Data URLs.

  1. getUploadedFilesByUser(username: string):
    • Builds the path to the user’s upload folder.
    • Call getImageDataURLs(uploadDir) to fetch image data in that folder.
  2. getImageDataURLs(uploadedDir: string):
    • Reads the image files in the given directory.
    • Converts each image to a base64-encoded Data URL.
    • Returns an array of objects containing each image’s file name and its Data 
    • URL.

3. Add a route for get uploaded files inside src/file-routes.ts

Please add below code  for get uploaded files

import {

  getUploadedFiles

} from "./controller/files-upload-controller";

router.get("/upload/:username", getUploadedFiles);Code language: JavaScript (javascript)

This routes /upload/:username to get uploaded user’s files from folder.

API URL-   GET- http://localhost:8080/api/files/upload/:username

4.  Run the project 

npx ts-node src/server.ts

Conclusion

In this tutorial, we’ve learned how to upload multiple files on a website using a React file uploader and display a file upload progress bar to track the upload. We explored how to send files from the frontend (React) to the backend (Node.js) and update the upload progress bar in real-time as files are uploaded.

For instance, in an app where users upload photos, they can select multiple images, and as each image uploads, a progress bar will show how much of the file has been uploaded.

 If you are considering Node.js for your backend and need guidance on choosing the right NodeJS development company, you can refer to our detailed guide on How to Select the Right NodeJS Development Company?.

With React, you handle the file selection and display the progress bar, while on the Node.js side, tools like Multer manage the actual file uploads. This approach makes the file upload in React JS smoother and provides users with immediate feedback on their uploads.You can find the full source code for this React TypeScript file upload project with backend integration on GitHub. Feel free to explore, clone, and adapt it to your needs.

Seamless app development CTA using ReactJS

Author's Bio:

Sachin Kumar
Sachin Kumar

Sachin Kumar is a software engineer at Mobisoft Infotech with over five years of hands-on experience in developing scalable and high-performance applications. He combines deep technical expertise with a passion for solving real-world problems through clean, efficient code.