New to NodeJS and S3, I wrote the following exploratory code to upload files to S3 via my NodeJS server without saving the file to disk or memory:
var express = require('express');
var Busboy = require('busboy');
var S3 = require('../utils/s3Util');
var router = express.Router(); // mounted at /uploads
router.post("/", function (req, res, next) {
let bb = new Busboy({ headers: req.headers });
const uploads = [];
bb.on('file', (fieldname, stream, filename, encoding, mimeType) => {
console.log(`Uploaded fieldname: ${fieldname}; filename: ${filename}, mimeType: ${mimeType}`);
uploads.push(S3.svc.upload({ Bucket: 'my-test-bucket', Key: filename, Body: stream }).promise());
});
bb.on('finish', () => {
console.log("# of promises:", uploads.length);
Promise.all(uploads).then(retVals => {
for (let i = 0; retVals && i < retVals.length; i++) {
console.log(`File ${i + 1}::`, retVals[i]);
}
res.end();
}).catch(err => {
console.log("Error::", err);
res.status(500).send(`${err.name}: ${err.message}`);
});
});
req.pipe(bb);
});
module.exports = router;
In the general failure case, how do I handle the scenario where the upload of 1 or more of x files being uploaded fails? Some uploads would have succeeded, some would have failed. However, in the catch
clause I wouldn't know which ones have failed...
It would be good to be able to make this upload process somewhat transactional (i.e., either all uploads succeed, or none do). When errors happen, ideally I would be able to "rollback" the subset of successful uploads.
You could do it like this:
Push an object into uploads, with the data you need to retry, so:
uploads.push({
fieldname,
filename,
mimeType,
uploaded: S3.svc.upload({ Bucket: 'my-test-bucket', Key: filename, Body: stream })
.promise()
.then(() => true)
.catch(() => false)
});
...
const failed = await
(Promise.all(uploads.map(async upload => ({...upload, uploaded: await upload.uploaded})))).then(u => u.filter(upload => !upload.uploaded))
const failedFiles = failed.join(', ')
console.log(`The following files failed to upload: ${failedFiles}`);
You need to make your event handlers async
to use await
inside them, so, for example:
bb.on('file', async (fieldname, stream, filename, encoding, mimeType) => {