How to compress image using Sharp JS after uploading with Multer - node.js

I feel like my code is complete frankenstein at this point but I do wonder why this formula does not work.
Once the file is uploaded, I can access the path with req.file.path and so I try to plug that into sharp but nothing happens. I'm not getting any errors or the expected result. (I'm using .rotate() to make it more obvious in testing)
var storage = multer.diskStorage({
destination: function(req, file, cb) {
cb(null, 'public/uploads')
},
filename: function(req, file, cb) {
cb(null, file.fieldname + '-' + Date.now() + path.extname(file.originalname))
}
});
var upload = multer({
storage: storage
});
router.post("/new", upload.single('image'), function(req, res) {
sharp(req.file.path).rotate();
var post = {
title: req.body.title,
image: uploadedImage,
description: req.body.description,
body: req.body.body
}
Blog.create(post, function(err, newPost) {
if (err) {
console.log(err);
res.redirect("/")
} else {
res.redirect("/");
}
});
});

You either need to use async/await or Promises as the calls to sharp are asynchronous. You will also need to do something with the modified/rotated file like copying it to a Buffer or saving to a File. See these examples in the documentation.
// use an async function
router.post("/new", upload.single('image'), async function(req, res) {
await sharp(req.file.path).rotate().toFile('/path/to/file');
// ... rest of your code
});

Related

Node.js - Uploading both an image and a file with Multer?

I am working on a mod site, and I was able to create a method that uploads an image, but I was wondering if it's possible to also upload a zip file in the same method. I tried chaining on code for the file after creating a file model, but I get an unexpected field error. Here's the method I'm trying
router.post('/upload', uploadImage.single('image'), uploadFile.single('file'), IsLoggedIn, (req, res, next) => {
console.log(req.file);
var newImg = new Image({
data: req.file.filename
});
var newFile = new File({
data: req.file.filename
});
Mod.create({
name: req.body.name,
image: newImg.data,
game: req.body.game,
file: newFile.data
}, (err, newMod) => {
if (err) {
console.log(err);
}
else {
res.redirect('/mods');
}
});
});
And here are my storage engines
const imageStorage = multer.diskStorage({
destination: (req, file, cb) => {
cb(null, './public/images')
},
filename: (req, file, cb) => {
cb(null, file.originalname)
}
});
const fileStorage = multer.diskStorage({
destination: (req, file, cb) => {
cb(null, './public/images')
},
filename: (req, file, cb) => {
cb(null, file.originalname)
}
});
const uploadImage = multer({storage: imageStorage});
const uploadFile = multer({storage: fileStorage});
Is it possible to use 2 storage engines and chain 2 uploads in one method, or am I going about it all wrong? Any clarification or direction would be greatly appreciated.

How to prevent Multer File Upload when input validation fails

I'm creating a (POST) route to handle file uploads and also store some other properties to MongoDB in addition to the file path. The problem is when input validation fails, the file is still uploaded in the static(uploads) folder.
I'm using the Multer middleware for file uploads.
Setup
const storage = multer.diskStorage({
destination: (req, file, cb) => {
cb(null, "./uploads/");
},
filename: (req, file, cb) => {
cb(null, Date.now() + "-" + file.originalname);
}
});
const fileFilter = (req, file, cb) => {
if (file.mimetype === "image/jpeg" || file.mimetype === "image/png") {
cb(null, true);
} else {
//rejects storing a file
cb(null, false);
}
};
const upload = multer({
storage: storage,
limits: {
fileSize: 1024 * 1024 * 5
},
fileFilter: fileFilter
});
Fields
const seamUpload = upload.fields([
{ name: "stylePicture", maxCount: 1 },
{ name: "materialPicture", maxCount: 1 }
]);
Route
router.post("/", [auth, seamUpload], async (req, res) => {
const { error } = validateSeam(req.body);
if (error) return res.status(400).send(error.details[0].message);
const seam = new Seam({
stylePicture: req.files["stylePicture"][0].path,
materialPicture: req.files["materialPicture"][0].path,
description: req.body.description,
deliveryDate: req.body.deliveryDate,
customer: req.body.customerId
});
await seam.save();
res.send(seam);
});
Client(PostMan) Screenshot
Me as you faced with this problem.
one solution that I found is when your authentication in all modes seems error, in that case if you have a file from client, you can easily remove it like this:
if(req.file) {
fs.unlink(
path.join(__dirname, "go to root folder that 'req.file.path' leads to the file", req.file.path),
(err) => console.log(err));
}
or then in case that you got multi files, you should do the same way for each of them. If there is, I glad to here that.
I wish there be another way by using multer package for handling that.
good luck
You can have the validation middleware BEFORE the multer middleware.
That way, when the validation fails, all the subsequent middlewares would not be executed -- and thus, the files will not be uploaded.
Separate out the validation like so:
const validateSeamUpload = (req, res, next) => {
const { error } = validateSeam(req.body);
if (error) return res.status(400).send(error.details[0].message);
return next();
};
And then, mount this validateSeamUpload BEFORE the seamUpload middleware like so:
router.post("/", [auth, validateSeamUpload, seamUpload], async (req, res) => {
/** No need for validation here as it was already done in validateSeamUpload */
const seam = new Seam({
stylePicture: req.files["stylePicture"][0].path,
materialPicture: req.files["materialPicture"][0].path,
description: req.body.description,
deliveryDate: req.body.deliveryDate,
customer: req.body.customerId
});
await seam.save();
res.send(seam);
});
By the way, you can pass them as arguments to post() as well. Like so:
router.post("/", /** => See, no need for an array */ auth, validateSeamUpload, seamUpload, async (req, res) => {
/** your controller code */
});

Rename uploaded images using MULTER

I am uploading the images using multer. They all are given random names (dec93b9f333c7a731723b06ce73c0bbc.jpg), which is very bad for SEO... Can you guys help me out, how to save the images with the pattern: 'fixed-name'+'random-name'.extension. Then at least part of the file would be readable for the google. Thanks!
app.set('images', '/var/www/images');
app.use(app.get('images'), express.static(app.get('images')));
var multerForImage = multer({
dest: app.get('images'),
onParseStart: function (file) {
console.log("Started parsing file stream", file);
},
onFileUploadStart: function (file) {
console.log('File recieved: ', file.originalname);
},
onFileUploadComplete: function (file, req, res) {
console.log("File upload complete");
var path = app.get('images') + "/" + file.name;
var user = req.session.user;
res.json({
success: true,
data: path
});
},
onFileUploadData: function (file, data, req, res) {
console.log('Data recieved for file upload');
},
onParseEnd: function (req, next) {
console.log("Parsing data end for file upload");
}
});
You can use the storage configuration.
app.set('images', '/var/www/images');
app.use(app.get('images'), express.static(app.get('images')));
var storage = multer.diskStorage({
destination: function (req, file, cb) {
cb(null, app.get('images'))
},
filename: function (req, file, cb) {
const randomPart = uuidV4(); // use whatever random you want.
const extension = file.mimetype.split('/')[1];
cb(null, 'fixed-name'+ randomPart + `.${extension}`)
}
})
var multerForImage = multer({
storage: storage,
...

node.js multer rename uploaded file

I am trying to rename an image file uploaded with multer by the request parameters.
Here is my code:
router.route('/upload/:userid')
.post(multer({
dest: 'uploads/'
}), function(req,res){
fs.readFile('uploads/' + req.files.file.name, function(err, data) {
fs.writeFile('uploads/' + req.params.userid + '.' + req.files.file.extension, data, function(err) {
fs.unlink('uploads/' + req.files.file.name, function(){
if(err) throw err;
});
});
});
res.json({ message: 'Successfully uploaded image!' });
});
It works great but I was wondering if it exists something cleaner and easier with multer rename function.
It already tried something like this:
router.route('/upload/:userid')
.post(multer({
dest: 'uploads/',
rename: function(req,res) {
return req.params.userid
}
}), function(req,res){
res.json({ message: 'Successfully uploaded image!' });
});
But it does not work because req is not populated yet (undefined).
I use httpie to test my code with the following command:
http.exe -f POST http://localhost:8080/upload/171284 file#D:\....\cat.jpg
Is it possible to use rename function of multer to do what I do with fs?
Or is there a better way?
Thank you for your feedbacks.
Thomas
EDIT
My new code using diskStorage:
var storage = multer.diskStorage({
destination: function (req, file, cb) {
cb(null, 'uploads/')
},
filename: function (req, file, cb) {
cb(null, req.params.userid + '-')
}
})
var upload = multer({ storage: storage })
router.route('/upload/:userid')
.post(multer({
storage: storage
}), function(req,res){
res.json({ message: 'Successfully uploaded image!' });
});
That throws an error:
Error: Route.post() requires callback functions but got a [object Object]
Ther is no rename in Multer constructor, insted of that, there is a filename in DiskStorage.
var storage = multer.diskStorage({
destination: function (req, file, cb) {
cb(null, '/tmp/my-uploads')
},
filename: function (req, file, cb) {
cb(null, file.fieldname + '-' + Date.now())
}
})
var upload = multer({ storage: storage })
filename is used to determine what the file should be named inside
the folder. If no filename is given, each file will be given a
random name that doesn't include any file extension.

Transform upload with NodeJS Multer

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
});
})
})
}
});

Resources