Node.js can receive browser file uploads with a small HTTP server and a multipart-form parser such as Formidable. On the client, the same endpoint can be called either by a normal HTML form submission or asynchronously with FormData and fetch().

This tutorial builds both versions. Node's fs module is built into Node.js, so it does not need to be installed from npm. Formidable is the external dependency that parses the incoming multipart/form-data request.

Step-by-step Node.js file upload example

The basic steps in this example to upload a file with Node.js and JavaScript follow this order:

  1. Ensure Node.js is installed locally
  2. Create a file named upload.js
  3. Install Formidable and import Node.js's built-in fs module
  4. Use Node.js to parse the incoming file and move it to a preferred folder
  5. Create an HTML upload form in a file named index.html
  6. Run the Node.js server and use the HTML form to upload a file
  7. Optionally configure the HTML page to upload with Ajax and JavaScript

Create the Node.js web server

First, verify that Node.js is installed. The exact version shown by your machine will vary. For a new project, use a currently supported Node.js LTS release.

npm init -y
npm install formidable

Node.js file uploader JavaScript implementation

Now update the server to use Formidable and Node's built-in filesystem modules. The example accepts uploads only at POST /upload, creates the destination directory if needed and generates the stored filename on the server.

In the createServer method, we will create an instance of Formidable’s IncomingForm object, which handles the intricacies of the file upload.

Formidable first writes the incoming file to temporary storage. The application then moves it into a local uploads directory. Avoid using the browser-supplied filename directly as a filesystem path.

When the operation is complete, it sends a Node.js File Upload Success message to the browser.

const http = require("node:http");
const fs = require("node:fs");
const path = require("node:path");
const crypto = require("node:crypto");
const formidable = require("formidable");

const uploadDir = path.join(__dirname, "uploads");
fs.mkdirSync(uploadDir, { recursive: true });

const server = http.createServer((req, res) => {
  if (req.method !== "POST" || req.url !== "/upload") {
    res.writeHead(404, {
      "Content-Type": "text/plain; charset=utf-8"
    });
    res.end("Not found");
    return;
  }

  const form = new formidable.IncomingForm({
    maxFileSize: 10 * 1024 * 1024
  });

  form.parse(req, (error, fields, files) => {
    if (error) {
      res.writeHead(400, {
        "Content-Type": "text/plain; charset=utf-8"
      });
      res.end("Upload failed");
      return;
    }

    const uploaded = Array.isArray(files.fileupload)
      ? files.fileupload[0]
      : files.fileupload;

    if (!uploaded) {
      res.writeHead(400, {
        "Content-Type": "text/plain; charset=utf-8"
      });
      res.end("No file was uploaded");
      return;
    }

    const extension =
      path.extname(uploaded.originalFilename || "");
    const storedName =
      `${crypto.randomUUID()}${extension}`;
    const destination =
      path.join(uploadDir, storedName);

    fs.rename(uploaded.filepath, destination, (renameError) => {
      if (renameError) {
        res.writeHead(500, {
          "Content-Type": "text/plain; charset=utf-8"
        });
        res.end("Could not save upload");
        return;
      }

      res.writeHead(201, {
        "Content-Type": "text/plain; charset=utf-8"
      });
      res.end("Node.js file upload succeeded");
    });
  });
});

server.listen(3000, () => {
  console.log("Server running at https://localhost:3000");
});

Save this file and then run the upload.js file again.

node upload.js

Then refresh the index.html page in the browser, select a file and click submit. The Node.js file upload process successfully stores the file to the uploads/ folder.

Add Ajax with FormData and fetch

At this point, the Node.js file upload component is feature complete. However, some people like to perform an Ajax based JavaScript upload from the client to avoid needless request-response cycles in the browser. To do an Ajax and JavaScript file upload to Node.js, replace the form in the HTML page with these two lines:

<input id="fileupload" type="file" name="fileupload">
<button id="upload-button" type="button">Upload</button>
<p id="upload-status" aria-live="polite"></p>

And add the following script before the end body tag:

async function uploadFile() {
  const fileInput = document.getElementById("fileupload");
  const status = document.getElementById("upload-status");
  const file = fileInput.files[0];

  if (!file) {
    status.textContent = "Choose a file first.";
    return;
  }

  const formData = new FormData();
  formData.append("fileupload", file);

  try {
    const response = await fetch(
      "https://localhost:3000/upload",
      {
        method: "POST",
        body: formData
      }
    );

    if (!response.ok) {
      throw new Error(`Upload failed: ${response.status}`);
    }

    status.textContent = "Upload successful.";
  } catch (error) {
    status.textContent = "Upload failed.";
    console.error(error);
  }
}

document
  .getElementById("upload-button")
  .addEventListener("click", uploadFile);

Save index.html and refresh the browser. The browser now sends the file asynchronously, so the page does not need a normal form navigation.

If index.html is served from a different origin than the Node.js server, the browser's same-origin policy applies and the server must explicitly allow the appropriate origin with CORS headers. Serving the page and upload endpoint from the same application avoids that extra configuration.

For production uploads, validate file types from trusted server-side inspection, enforce size limits, generate server-controlled filenames, authenticate uploaders when appropriate and keep uploaded files outside executable or publicly writable application directories. Malware scanning may also be appropriate.

And that’s how easy it is to create a Node.js file uploader in JavaScript.