Uploading a folder via HTML form or cURL

References:

Contents of folder to be uploaded (drawn using tree --charset unicode --dirsfirst -a -n in bash):

  C:\Users\me\Desktop\My Folder
  |-- subfolder
  |   `-- b.txt
  `-- a.png

Rendered HTML form (drawn using https://asciiflow.com/legacy):

  TEST FORM

  Choose a folder to upload (can contain subfolders and files):
  +--------------+
  | Choose Files |  No file chosen
  +--------------+

  Type out the full path of the chosen folder on your computer:
  (browser security does not allow us to get that info)
  +---------------------------------------------------+
  | C:\Users\me\Desktop\My Folder                     |
  +---------------------------------------------------+

  +--------+
  | Submit |
  +--------+

HTML code:

  <html>
    <head></head>
    <body>
      <h1>TEST FORM</h1>

      Choose a single folder to upload: <!-- "multiple" doesn't allow many folders -->
      <form id="myform" enctype="multipart/form-data" method="POST"
        action="http://localhost:3000/upload">
        <input id="filepicker" name="myfiles[]" type="file" multiple webkitdirectory />
        <input name="myfilepaths[]" type="hidden" value="" />
        <br><br>
        Type out the full path of the chosen folder on your computer:<br>
        (browser security does not allow us to get that info)<br>
        <input name="myfolderpath" type="text"
               placeholder="C:\Users\me\Desktop\My Folder" /><br><br>
        <input type="submit" />
      </form>

      <script>
        // Tis needed cos uploaded files only have filenames not subfolder paths
        document.getElementById('filepicker').addEventListener('change', (event) => {
          // Remove all previous myfilepaths[] elements especially if user changes mind
          let filePathElements = document.querySelectorAll('[name="myfilepaths[]"');
          [...filePathElements].forEach((element) => {
              element.remove();
          });

          // Create new hidden input for each file in folder to be uploaded
          // This sends an array of paths in the same order as the files uploaded
          let formElement = document.getElementById('myform');
          [...event.target.files].forEach((file) => {
              let element = document.createElement('input');
              element.type = 'hidden';
              element.name = 'myfilepaths[]';
              element.value = file.webkitRelativePath;
              formElement.appendChild(element);
          });
        }, false);
      </script>
    </body>
  </html>

How to use cURL? You cannot specify a folder in the command, but you can specify multiple files. cURL automatically sets the “Content-Type: multipart/form-data” header when the -F option is set. Note that for Windows, the values may need to be enclosed in double quotes instead of single quotes.

  curl -L -X POST 'http://localhost:3000/upload' \
       -F 'myfiles[]=@C:/Users/me/Desktop/My Folder/a.png' \
       -F 'myfiles[]=@C:/Users/me/Desktop/My Folder/subfolder/b.txt' \
       -F 'myfolderpath=C:/Users/me/Desktop/My Folder' \
       -F 'myfilepaths[]=My Folder/a.png' \
       -F 'myfilepaths[]=My Folder/subfolder/b.txt'

How a Node.js server would receive the form data and uploaded files:

  ### Install needed packages: npm install express express-fileupload

  ### Source code in index.js
  const express = require('express');
  const expressFileUpload = require('express-fileupload');

  let app = express();
  app.use(express.json()); // handle application/json
  app.use(express.urlencoded({ // handle application/x-www-form-urlencoded
      extended: true
  }));
  app.use(expressFileUpload({ // uploaded files made available via request.files
      useTempFiles : true, // use temp files instead of memory
      tempFileDir : '/tmp/' // standard Linux directory for temp files
  }));

  app.post('/upload', (request, response, next) => {
      console.log('body', request.body);
      console.log('files', request.files); 

      // If using temp files, run fs.unlink(file.tempFilePath) for each file
      response.status(200).json({ timestamp: Date.now() });
  });

  (async function () {
      let port = 3000;
      let server = app.listen(port, () => {
          console.log(
              `Server started on port ${port}: filename=${__filename}`
              + ` NODE_ENV=${process.env.NODE_ENV}`
          );

          app.emit('test.app.ready', { // emit event to indicate app is ready
              timestamp: Date.now()
          });
      });
      server.keepAliveTimeout = 0;
  })();

  ### Console output from running `node index.js`
  body
  {
    myfolderpath: 'C:\\Users\\me\\Desktop\\My Folder',
    'myfilepaths[]': [ 'My Folder/a.png', 'My Folder/subfolder/b.txt' ]
  }

  files
  {
    'myfiles[]':[
      {
        name: 'a.png',
        data: <Buffer 89 50 4e 47 0d 00 49 48 44 52 ... >,
        size: 558,
        encoding: '7bit',
        tempFilePath: '',
        truncated: false,
        mimetype: 'image/png',
        md5: 'ccac7bd9f12a4bae50dbbc86c8dd8037',
        mv: [Function: mv]
      },
      {
        name: 'b.txt',
        data: <Buffer 48 65 6c 6c 6f 20 57 6f 72 6c 64 0a>,
        size: 12,
        encoding: '7bit',
        tempFilePath: '',
        truncated: false,
        mimetype: 'text/plain',
        md5: 'e59ff97941044f85df5297e1c302d260',
        mv: [Function: mv]
      }
    ]
  }

Bonus: How to upload files using Node.js besides using HTML form or cURL:

  ### Install needed packages: npm install form-data

  ### Source code in upload-to-somewhere.js
  const FormData = require('form-data');
  const fs = require('fs');
  const http = require('http');

  let form = new FormData();
  form.append(
      'myfiles[]', 
      fs.createReadStream('C:/Users/Me/Desktop/My Folder/a.png')
  );
  form.append(
      'myfiles[]', 
      fs.createReadStream('C:/Users/Me/Desktop/My Folder/subfolder/b.txt')
  );
  form.append('myfolderpath', 'C:/Users/Me/Desktop/My Folder');
  form.append('myfilepaths[]', 'My Folder/a.png');
  form.append('myfilepaths[]', 'My Folder/subfolder/b.txt');

  let options = {
      method: 'POST',
      headers: form.getHeaders(),
  };

  let request = http.request('http://localhost:3000/upload', options, (response) => {
      console.log(response.body);
  });

  request.on('response', (response) => {
      console.log(response.statusCode);
  });

  form.pipe(request);
  // do not use request.end() when pipe() is used