Can't send back object in multer method - node.js

I'm performing a file upload via multer, and since I want to store the file in a specific location, as well as name it my own file name, I am using the destination and filename attributes that multer offers when creating the storage object.
The problem I'm having is I want to send back to the client the information of my newly created object after storing it in the database. However, there is no res parameter to do this and I can only do this in my post method, which does not have the object I just created.
var storage = multer.diskStorage({
destination: function (req, file, cb) {
cb(null, './uploads'); // Absolute path. Folder must exist, will not be created for you.
},
filename: function (req, file, cb) {
var fileType = file.mimetype.split("/")[1];
var fileDestination = file.originalname + '-' + Date.now() + "." + fileType;
cb(null, fileDestination);
var map = new Map({
mapName: req.body.mapTitle,
mapImagePath: "./uploads/" + fileDestination,
ownerId: req.user._id
});
Map.createMap(map, function(err, map){
if(err)
next(err);
console.log(map);
});
}
});
var upload = multer({ storage: storage });
router.post('/', upload.single('mapImage'), function (req, res) {
res.status(200).send({
code: 200, success: "Map Created."
});
});

Multer attaches the files to the request object, you have access to these in your post method:
app.post('/', upload.single('mapImage'), function (req, res, next) {
console.log(req.file.filename); // prints the filename
console.log(req.file.destination); // prints the directory
console.log(req.file.path); // prints the full path (directory + filename)
console.log(req.file.originalname); // prints the name before you renamed it
console.log(req.file.size); // prints the size of the file (in bytes)
res.json(req.file);
});

Related

Nodejs Express Multer file upload - handle response in route based on form data

My current implementation is as follows:
var storage = multer.diskStorage({
destination: function (req, file, cb) {
cb(null, 'public/images/items/')
},filename: function (req, file, cb) {
let ext = '';
if (file.originalname.split(".").length>1)
ext = file.originalname.substring(file.originalname.lastIndexOf('.'), file.originalname.length);
cb(null, Date.now() + ext)
}
})
...Using route....
app.post('/updateItemImage', upload.single('image'), function (req, res, next) {
console.log('user: ' + req.user.id + ' updating image: ' + req.body.item_id);
})
..Alright. That works. req.body.item_id is present in my route, and multer handles req.file, saving it to disk with a unique filename.
however...
I'd like the item to be saved using multer, only if req.user.id and req.body.item_id have certain values. req.body.item_id is undefined within the scope of:
filename: function (req, file, cb) {
So I can't move my code into this function.
TLDR: post function needs to capture req.body.item_id, and req.file. If req.body.item_id == value than save file and res.send('ok') There's more than one way to skin a cat. What's an option that would work?
EDIT: here is the frontend js:
$scope.uploadFile = function(files) {
var fd = new FormData();
var uploadUrl = '/updateItemImage';
var fd = new FormData();
fd.append("image", files[0]);
fd.append("item_id", '11');
$http.post(uploadUrl, fd, {
transformRequest: angular.identity,
headers: {'Content-Type': undefined}
}).then(function (response) {
if (response.data == 'ok') {
return
}
alert('something went wrong')
})
}
EDIT: Swapping the order of parameters so that the body param was first:
fd.append("item_id", '11');
fd.append("image", files[0]);
Resolved my issue. This shouldn't matter!
req.body.item_id is undefined within the scope of:
filename: function (req, file, cb) {
I've already tested it, it worked, the value is not undefined in that scope

How to get a wav file from a POST body to upload it using Node JS/Express

I am creating a node js app who's purpose is, in part, receiving a POST request and then parsing the body for 2 fields: id and a .wav file.
Unfortunately, I can't seem to parse the req body correctly. I believe the request should be multipart/form-data (which is why I'm using multer and not bodyParser), but I keep getting undefined when I try the log(body.req.file) and the res shows the 500 internal server error I send in the app.post function.
Here is the server code...
Multer part :
var upload = multer({ storage: storage })
var uploadFields = upload.fields([{name: 'id'}, {name: 'file'}])
var storage = multer.diskStorage({
destination: function(req, file, cb){
cb(null, 'temp/')
},
filename: function(req, file, cb){
fileBody = file
keyName = `${req.body.id}.` + getExtension(file)
var objectParams = {Bucket: myBucket, Key: keyName, Body: fileBody}
s3.putObject(objectParams).promise().then(function(res){console.log('wewlad')}).catch(function(err){console.log(err)})
cb(null, file.fieldname + '-' + Date.now() + '.' + getExtension(file))
}
})
Express part:
app.post('/', uploadFields, function(req, res){
console.log('POST to / recieved!')
console.log(req.body.id)
console.log(req.body.file)
fileBody = req.files
keyName = req.body.id + '.wav'
var params = {Bucket: myBucket, Key: keyName, Body: fileBody}
var putObjectPromise = s3.putObject(params).promise()
putObjectPromise.then(function(res){
console.log(res)
}).catch(function(err){
console.log(err)
})
})
a getExtension function I made (not sure if this is needed)...
function getExtension(file) {
var res = ''
//some of these are being used for testing
if (file.mimetype === 'audio/wav') res = '.wav'
else if (file.mimetype === 'audio/basic') res = '.ul'
else if (file.mimetype === 'multipart/form-data') res = '.wav'
else if (file.mimetype === 'application/octet-stream') res = '.ul'
else console.log(`ERROR: ${file.mimetype} is not supported by this application!`)
return res
}
Side-note: I am using post-man to send the id and file as form-data
Any help is greatly appreciated! Also, if there's anything I left out let me know!
EDIT:
Tried again with new info, this is my attempt:
var upload = multer({ dest: 'uploads/' }) //what do I put here to not store file
...
...
app.post('/', upload.single('file'), function(req, res, next){
console.log('POST to / received!')
console.log(req.body.id)
console.log(req.file)
var fileBody = req.file //and what do I put here to get access to the file
var keyName = `audio-${req.body.id}.${getExtension(req.file)}`
var params = { Bucket: myBucket, Key: keyName, Body: fileBody }
var putObjectPromise = s3.putObject(params).promise()
putObjectPromise.then(function(res){
console.log(res)
res.sendStatus(204);
}).catch(err =>{
console.log(err)
next(err)
})
});
it results in an "InvalidParameterType: Expected params.Body to be a string, buffer, stream, blob, or typed array object" that I is coming from S3 validating the params for the putObject call
Solution for anyone that passes by in the future:
var upload = multer()
app.post('/', upload.single('file'), function(req, res, next){
//for debugging purposes
console.log('POST to / received!')
console.log(req.body.id)
console.log(req.file)
//set the parameters for uploading the files to s3
var fileBody = req.file.buffer
var keyName = `audio_logs/audio-${req.body.id}.${getExtension(req.file)}`
var params = { Bucket: myBucket, Key: keyName, Body: fileBody }
//upload the audio file and then state when done. Log any errors.
var putObjectPromise = s3.putObject(params).promise()
putObjectPromise.then(function(res){
console.log('Uploaded -> ' + res)
}).catch(err =>{
console.log(err)
next(err)
})
});
As #shortstuffsushi pointed out, since you're using multer.fields, file metadata is parsed into req.files.
However, that object is not the actual file body.
It's a key->value map where each key is one of the fields you defined in multer.fields and each value is an array of file objects.
So if I'm reading your code correctly, you'll have to access your file as req.files['file'][0].
Additionally, since you configured multer to use DiskStorage, that file object won't contain the actual file body. Instead, it will have a path property which points to the file on your file system.
So in order to upload it to S3, you first have to read it from disk into a buffer or stream (depending on what the upload library can use) and then use that to actually upload the file's data.
If you are only uploading a single file per request, you should consider using multer.single instead of multer.fields. I suppose the id field is a number or string, so specifying that in multer.fields only makes it try to parse nonsense into a file.
And with multer.single the file metadata will be parsed into req.file (no 's'), so you don't need the extra "map into array" access.
So a full example would look like this:
const readFile = require('util').promisify(require('fs').readFile)
app.post('/', upload.single('file'), function(req, res, next){
console.log('POST to / received!')
console.log(req.body.id)
console.log(req.file)
readFile(req.file.path)
.then(body => {
const params = { Bucket: myBucket, Key: req.body.id + '.wav', Body: body }
return s3.putObject(params).promise();
})
.then(result => {
console.log(result)
res.sendStatus(204);
})
.catch(err => {
console.error(err);
next(err);
});
});
And just as #shortstuffsushi mentioned, the filename config for multer should not upload the file as well.
Converting to an answer for your first problem, multer uses the files property on the request, not body.file. Check out their docs for more.

How to get req.body parameters in multer s3

var upload = multer({
storage: multerS3({
s3: s3,
bucket: 'bucket',
metadata: function(req, file, cb) {
cb(null, {
fieldName: file.fieldname
});
},
key: function(req, file, cb) {
console.log('req.body', req.params.id); //not getting
console.log('req.body', req.body);
//Not getting param here that passed in api
//Need to save file on s3 at specific location i.e /foldername/filename
//But the folder name not getting from API
cb(null, file.originalname)
}
}) }).array('userFile', 1);
Above is multer s3 code
app.post('/saveData', function(req, res, next) {
upload(req, res, function(err) {
console.log('err' + err);
var status = '';
var result = '';
var link = '';
if (err) {
status = false;
} else {
status = true;
}
result = {
"status": status,
"link": link
}
});
res.send(result); });
Above code where calling multer upload function. I am parsing data in API (from Angular2, set -Content-Type": "multipart/form-data) as formdata
let formData: FormData = new FormData();
formData.append('userFile', file);
formData.append('fileName', fileName);
I require the req.body data from API like folder name and other, so I can put the file to specific place on S3. Need req.body data inside the key: function(req, file, cb) { of multerS3.
It seems to be a bug in multer, but as said here you can reorder the parameters when sending.
https://github.com/expressjs/multer/issues/322#issuecomment-191642374
Reading the source code of dropzone.js i've figured out that the problem is that S3 expects the file to be the last paramater of your formdata
If your file input is the last input of your form, you should also try to pass the reference of the form tag when you make a new instance of FormData()
<form
id="myForm"
className="col-md-10 col-md-offset-1"
>
so pass id like this: new FormData(document.getElementById(myForm)
then you should find in your key callback function req.body with the input values of your form

Format issue while uploading Jpg file with NodeJS Multer module

I'm using Multer to upload jpg file from Postman to a node.js back-end.
I assume the "upload" goes well because the image is located at the right place with the right name and the right size after the POST request
Unfortunately, when I try to open the image file it doesn't open as a jpg image even though the extension is .jpg
I used checkFileType website and apparently, the file type becomes application/octet-stream after the upload.
I didn't specify any headers in the request.
Here is my Postman setup
Here is the response
Here is the code in the Node API side
var localWorkLocation = '/Users/noste/CarFixer/Pictures/';
exports.createBinary = function (req, res) {
var location = localWorkLocation + req.params.platenumber;
var storageOptions = multer.diskStorage({
destination: function (req, file, callback) {
mkdirp(location, err => callback(err, location));
callback(null, location);
},
filename: function (req, file, callback) {
//if (!file.originalname.match(/\.(jpg|jpeg|png)$/)) {
// return callback(new Error('Only image files are allowed!'), false);
//}
callback(null, file.originalname);
},
});
var upload = multer({storage : storageOptions}).single('picture');
upload(req, res, function(err){
console.log(req.file);
if(err) {
return res.end(err.message);
}
res.send(req.file);
});
};
I've tried to upload a .txt file and it's still readable after upload.
Is there a place where I need to specify that I want to keep the jpg format ?
here's the storage configuration u need .. change the naming according to your code
var path = require('path');
var storage = multer.diskStorage({
destination: function(req, file, callback) {
callback(null, './uploads');
},
filename: function(req, file, callback) {
var fname = file.fieldname + '-' + Date.now() + path.extname(file.originalname);
callback(null, fname);
}
});
And here's another answer for multiple files upload

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.

Resources