Transform upload with NodeJS Multer - node.js

I'm currently implementing a file/image upload service for my users. I want to transform these images (resize/optimize) before uploading to my s3 bucket.
What I'm currently doing: Using a multipart form on my frontend (I think the actual implementation doesn't matter here..) and the multer and multer-s3 packages on my backend.
Here my implementation stripped down to the important parts.
// SETUP
var multer = require('multer');
var s3 = require('multer-s3');
var storage = s3({
dirname: 'user/uploads',
bucket: auth.aws.s3.bucket,
secretAccessKey: auth.aws.s3.secretAccessKey,
accessKeyId: auth.aws.s3.accessKeyId,
region: auth.aws.s3.region,
filename: function (req, file, cb) {
cb(null, Date.now());
}
});
var upload = multer({storage: storage}).single('img');
// ROUTE
module.exports = Router()
.post('/', function (req, res, next) {
upload(req, res, function (err) {
if (err) {
return res.status(401).json({err: '...'});
}
return res.json({err:null,url: '..'});
});
});
What I want to do: transform the image before uploading it. I'm not sure if I need to use multer/busboy here or I can just do it with NodeJS (thus I've tagged NodeJS and express as well).
So my question is: where can I intercept the upload and transform it before uploading it to my S3 bucket?

Not sure if you're still looking for an answer to this, but I had the same problem. I decided to extend the multer-s3 package.
I've opened a pull request to the original repository, but for now, you can use my fork.
Here's an example of how to use the extended version:
var upload = multer({
storage: multerS3({
s3: s3,
bucket: 'some-bucket',
shouldTransform: function (req, file, cb) {
cb(null, /^image/i.test(file.mimetype))
},
transforms: [{
id: 'original',
key: function (req, file, cb) {
cb(null, 'image-original.jpg')
},
transform: function (req, file, cb) {
cb(null, sharp().jpg())
}
}, {
id: 'thumbnail',
key: function (req, file, cb) {
cb(null, 'image-thumbnail.jpg')
},
transform: function (req, file, cb) {
cb(null, sharp().resize(100, 100).jpg())
}
}]
})
})
EDIT: My fork is also now available via npm under the name multer-s3-transform.

I've tried using #ItsGreg's fork, but couldn't get it to work. I managed to get this behaviour working by using multer-s3 standard configuration, and inside my file upload endpoint, i.e.,
app.post('/files/upload', upload.single('file'), (req, res) => {...})
I am retrieving the file using request, and passing the Buffer to sharp. The following works (and assumes you are using ~/.aws/credentials):
let request = require('request').defaults({ encoding: null });
let dataURI = `https://s3.amazonaws.com/${process.env.AWS_S3_BUCKET}/${image.defaultUrl}`;
request.get(dataURI, function (error, response, body) {
if (! error && response.statusCode === 200) {
let buffer = new Buffer(body);
const sizes = ['thumbnail', 'medium', 'large'];
sizes.forEach(size => {
sharp(buffer)
.resize(image.sizes[size])
.toBuffer()
.then(data => {
// Upload the resized image Buffer to AWS S3.
let params = {
Body: data,
Bucket: process.env.AWS_S3_BUCKET,
Key: `${image.filePath}${image.names[size]}`,
ServerSideEncryption: "AES256",
};
s3.putObject(params, (err, data) => {
if (err) console.log(err, err.stack); // an error occurred
else console.log(data); // successful response
});
})
})
}
});

Related

Storing MongoDB ID as my key on Amazon S3

I've built functionality to simultaneously save a record in my MongoDB database and upload image(s) to Amazon S3. That's all working OK. However, in this process, I'm also wanting to save the ID of the data from my MongoDB as the key in my Amazon S3 upload.
I've tried playing around with 'req.params.id' and so on, but it's just coming back as undefined.
Is there a really obvious way of doing this? Hoping I'm missing something straightforward.
Code:
const upload = multer({
storage: multerS3({
s3: s3,
bucket: 'example',
metadata: function (req, file, cb) {
cb(null, {fieldName: file.fieldname});
},
contentType: multerS3.AUTO_CONTENT_TYPE,
acl: 'public-read',
key: function (req, file, cb) {
cb(null, `${req.body.id}`
// Date.now().toString()
);
}
})
});
module.exports = (req, res) => {
HouseListing.create(req.body, (error, houselisting) => {
if (error) {
const validationErrors = Object.keys(error.errors).map(key => error.errors[key].message);
req.flash('validationErrors', validationErrors);
return res.redirect("/houseListing");
}
console.log(req.files);
console.log(req.body.mainImages);
console.log(req.body.id);
res.redirect("/");
});
};
The bottom two console logs of my Database function are returning 'undefined'. As is the upload function.
Any tips? :) Please let me know if I've missed any info out!

Multer S3 passing back form data and other data in one call

Everything I've looked at regarding s3 and multer s3 looks something like this
const upload = multer({
storage: multerS3({
s3: s3,
bucket: 'bucket',
acl: 'public-read',
cacheControl: 'max-age=31536000',
contentType: multerS3.AUTO_CONTENT_TYPE,
metadata: function (req, file, cb) {
cb(null, { fieldName: file.fieldname });
},
key: function (req, file, cb) {
const key = `pictures/${Date.now().toString()}`;
cb(null, key);
},
}),
});
and then the controller action looks like this
app.post('/profileImage', upload.single('profileImage'), async function (req, res) {
const { _id } = req.params;
const user = await User.findOneAndUpdate({ _id }, { image: req.file.location }, { new: true });
res.json(user);
});
My question is can I send back form data and other data in one call and upload my images to s3. I want to be able to structure my data something like this
const formData = new FormData();
formData.append("picture", this.file);
const data = {
title: this.toDoTitle,
details: this.toDoDetails,
formData,
};
axios
.post("/profileImage", data)
.then(() => {
})
.catch(err => {
console.log(err);
});
and then in my controller be able to save that form data image in s3 but also interact with the other data. I've been able to send back data in the req.params but I want to know if you can send it back in the body as well with the image and be able to upload image and get other data

Express, Nodejs use Multer for different Services

I am using express server and multer for upload file on different services (local, azure, cloudinary, amazon s3 etc).
For that i am using different module of multer multer-azure, multer-cloudinary etc.
I need this configuration will be applied to user wise and that information comes from the database.
So i need a extra call to fetch data from database before multer come in action.
I am able to call database query but when i am trying to call multer function, req parameter coming blank. Here is what i am doing.
var multerUtility = require('./upload/multer.utility');
let multer = new multerUtility().getActiveMulterService();
router.post('/', getMetadataConfiguration, multer, (req, res, next) => {
console.log('========== req ==========', req.file); // It is coming blank
console.log('========== req ==========', req.body); // It is coming blank
});
Here is first middleware function, which fetch data from database to verifiy which service will use to upload file.
function getMetadataConfiguration(res, req, next) {
var conn = new jsforce.Connection({
loginUrl : config.org_url,
});
var records = [];
conn.login(username, password, function(err, userInfo) {
if (err) {
return console.error(err);
}
conn.query("query", (err, result) => {
if(err) {
res.status(500).send(err);
}
console.log('=========== result=========', result);
req.serviceConfig = result.records[0];
next();
});
});
}
And here is my MulterUtility Class to handle configuration:
upload/multer.utility.js
class MulterUtility {
constructor() {
}
getActiveMulterService(req, res, next) {
var multerConfiguration;
if(req.serviceConfig.service == 'azure') {
multerConfiguration = multer({
storage: multerAzure({
connectionString: config.azure.connectionString,
account: config.azure.account,
key: config.azure.key,
container: config.azure.container
})
}).single('image');
} else if(req.serviceConfig.service == 'cloudinary') {
multerConfiguration = multer({
storage: cloudinaryStorage({
cloudinary: cloudinary,
folder: config.storageFolder
// allowedFormats: ['jpg', 'png', 'jpeg']
})
}).single('image');
} else if(req.serviceConfig.service === 'amazon') {
multerConfiguration = multer({
storage: multerS3({
s3: s3,
bucket: 'mycontainer',
acl: 'public-read',
contentType: multerS3.AUTO_CONTENT_TYPE,
metadata: function (req, file, cb) {
cb(null, {fieldName: file.fieldname});
},
key: function (req, file, cb) {
cb(null, Date.now().toString() + '-' + file.originalname)
}
})
}).single('image');
} else if(req.serviceConfig.service === 'local') {
multerConfiguration = multer({
storage: multer.memoryStorage()
}).single('image');
}
return multerConfiguration;
}
}
module.exports = MulterUtility;
After executing multer, i am not recieving a req.file or req.body params what multer sets after uploading file.
For now you can consider the 'local' file upload as mentioned in last condition.
The problem is that you call the method getActiveMulterService once before the router. But you need to call it for each post. Try something like this:
var multerUtility = require('./upload/multer.utility');
let multers = new multerUtility();
router.post('/',
getMetadataConfiguration,
(req, res, next) => multers.getActiveMulterService(req, res, next)(req, res, next),
(req, res, next) => {
console.log('========== req ==========', req.file); // It is coming blank
console.log('========== req ==========', req.body); // It is coming blank
});
And in this function you have arguments in the wrong order:
getMetadataConfiguration(res, req, next)
// ==>
getMetadataConfiguration(req, res, next)
Hi this has largely been answered already. The solution is to manual add your file object back onto your req.body object during the process.
Full solution is found here

I am not able to upload large files using multer-s3. It is not giving me any error as well.

I am not able to upload large files using multer-s3. It is not giving me any error as well. It just doesn't upload the file, doesn't even enters the callback and gets timeout. Any way to handle uploading large files to s3 Bucket?
I am using it like this:
var uploadSingle = upload.single('uploadFile');
router.post('/uploadVideo',function(req,res,next){
uploadSingle(req,res,function(err){
// doesn't come here if the file is large
if(err){
//Error Response , Error while uploading Module PDF;
}
else{
//handling file upload
// success response
}
});
}
I had the same issue and after researching this page I found that I need to add contentLength as one of the parameters. Its value is for the length in bytes.
const s3 = new AWS.S3({
accessKeyId: process.env.S3_ACCESS_KEY_ID,
secretAccessKey: process.env.S3_SECRET_ACCESS_KEY
});
var upload = multer({
storage: multerS3({
s3: s3,
bucket: 'myBucket',
contentType: multerS3.AUTO_CONTENT_TYPE,
contentLength: 500000000,
metadata: function (req, file, cb) {
cb(null, {fieldName: file.fieldname});
},
key: function (req, file, cb) {
cb(null, file.originalname);
}
})
});
router.post('/uploadToS3', upload.array('photos', 30), function(req, res, next) {
res.send({"message": 'Successfully uploaded ' + req.files.length + ' files!'});
})

Attributes for image stored using Google Cloud Storage

I am using the NPM documentation for uploading an image to Google Cloud Storage
app.post('/test', upload.single('image'), function (req, res, next) {
});
let bucket = gcs.bucket('bucket-name');
bucket.upload(req.file.path, function(err, file) {
if (err) {
throw new Error(err);
}
console.log(file);
});
console.log(req.file);
// req.body will hold the text fields, if there were any
})
Is there any way I can specify a custom name and file type for the file I'm uploading? In my case it should be an image.
You can pass options to your upload method:
var options = {
destination: 'my-image-name.jpg',
metadata: {
contentType: 'image/jpeg',
}
};
bucket.upload(req.file.path, options, function(err, file) {
});

Resources