References:
- Upload directory doesn’t send folder name with file
- Upload files with CURL
- Multipart formposts
- How to upload folder using HTML and PHP
- VGG Image Annotator: Issue 270 – Using Webkitdirectory to import multiple directories and maintain directory hierarchy
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