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

