Some properties missing in req.body inside multer middleware - node.js

I have a client application that posts some formData to my node backend. The data coming from the client is initially an object that contains a files property among others. Before posting to the back-end, I convert the object to formData.
// client.js file
const person = {
email: 'johndoe#gmail.com',
age: 19,
files: [File, File, File],
occupation: '',
status: 'active',
}
person.occupation = this.getOccupation(); // getOccupation() is defined somewhere in the file
const formData = new FormData();
Object.entries(person).forEach(([key, value]) => {
if (key === "files") {
for (let i = 0; i < person[key].length; i++) {
formData.append("files", person[key][i]);
}
} else {
formData.append(key, value);
}
});
// The following logs output the correct information so we're good until this point
console.log(formData.getAll("files"));
console.log(formData.get("occupation"));
console.log(formData.get("status"));
this.submitData(formData); // Posts to the backend
The data is posted to a route in the node backend with the multer middleware - uploadFiles().
const submitDataOptions = {
storage: multer.diskStorage({
destination: "./public/uploads/users/files",
filename: function (req, file, cb) {
console.log('Request body');
console.log(req.body);
console.log('Occupation');
console.log(req.body.occupation);
console.log('Status');
console.log(req.body.status);
const ext = file.mimetype.split("/")[1];
const datetimestamp = Date.now();
const fname = `${datetimestamp}.${ext}`;
cb(null, fname);
},
}),
};
uploadFiles() {
return multer(submitDataOptions).array("files");
}
The files get uploaded just fine but when I console.log(req.body), I notice that an object with only the properties email and age is returned. Logging occupation and status returns undefined in either case. The multer documentation says that req.body will contain the text fields if there were any but in this case I cannot find those.
What is the issue here?

req.body might not have been fully populated yet. It depends on the order that the client transmits fields and files to the server.
So just put the files last in your object:
const person = {
email: 'johndoe#gmail.com',
age: 19,
occupation: '',
status: 'active',
files: [File, File, File],
}

Related

POSTing from Angular to Node. Getting empty body on node

Im posting two fields in Angular to a NodeJs endpoint.
I usually post on the body and everything is perfect at Node, but this time, I have to post a form to upload a file.
So this is my code for posting the form data (Angular side):
var APIURL = sessionStorage.getItem('endPoint') + "profile/updateeocoverage";
let formData = new FormData();
formData.append("data", JSON.stringify(this.payLoad));
if (this.eofile) {
formData.append("myfile", this.eofile, this.eofile.name);
}
this.httpClient.post(APIURL, formData).subscribe(
result => {
....
My problem is that I always retrieved the body at node as follows:
router.post('/updateeocoverage', async (req, res, next) => {
console.log(req.body)
return;
....
But with the method Im using now in Angular, req.body is retrieving {}
Is the POST wrong, or the router wrong at Node side?
Thanks.
UPDATE ON PABLO'S ANSWER BELOW (SOLUTION PROVIDED) for whoever runs into this issue:
Using Multer solved the problem, but as he said some workaround is needed as to set the file name, but most important it is required to authenticate the user, so:
const multer = require('multer')
const path = require('path')
To authenticate the user, I send the authentication parameters on the header. Sending it as formdata.append didn't work for me. This sets true or false to upload the file, otherwise anyone can upload anything to the route:
async function authenticateUser(req, file, cb) {
let tempcred = JSON.parse(req.headers.data)
let credentials = tempcred.credentials;
let userData = await utils.isValidUser((credentials), false);
if (userData.isValid == false) {
cb(null, false)
return;
}
else {
cb(null, true)
}
}
Then, since Multer uploads the file with a random name, and I need to save it with the user ID name and the file extension, I do the following:
var storage = multer.diskStorage({
destination: function (req, file, cb) {
cb(null, 'uploads/eofiles/')
},
filename: async function (req, file, cb) {
let tempcred = JSON.parse(req.headers.data)
let credentials = tempcred.credentials;
let userid = await utils.decrypt(credentials.userid, process.env.SECRET);
cb(null, userid + path.extname(file.originalname))
}
});
Finally, I declare the upload variable for using it with Multer:
var upload = multer({ storage: storage, fileFilter: authenticateUser })
And set the router:
router.post('/updateeofile', upload.single("myfile"), async (req, res, next) => {
let filename = req.file.filename //gets the file name
...
...
do my stuff, save on database, etc
...
...
});
For the record, "myfile" is the input file id.
And this is how I upload the file from Angular:
var APIURL = sessionStorage.getItem('endPoint') + "eoset/updateeofile";
const httpOptions = {
headers: new HttpHeaders({
'data': `${JSON.stringify(this.payLoad)}`
})
};
let formData = new FormData();
if (this.eofile) {
formData.append("myfile", this.eofile, this.eofile.name);
}
this.httpClient.post(APIURL, formData, httpOptions).subscribe(
result => {
...
...
...
},
error => {
});
I spent 6 hours on this today. I hope this helps you and saves you some time.
Try using Multer
npm i multer
const multer = require('multer')
const upload = multer({ dest: 'uploads/' })
router.post('/updateeocoverage', upload.single('myfile'), function (req, res, next) {
res.json(JSON.parse(req.body.data))
})
You will need to indicate the destination folder (line 2) and work out the file name, extension, etc.
First thing to do is ensure that the body isn't empty before making the request, so do a console.log before the request:
console.log(formData);
this.httpClient.post(APIURL, formData).subscribe(
result => {
....
After that, if the body is filled correctly, try to put the body like this on the request method:
this.httpClient.post(APIURL, {formData})
or
this.httpClient.post(APIURL, {
"field1": formData.field1,
...
})
and if these two things don't correct your issue, problaby you have something wrong on back-end side

Is there a way to let users directly upload image on cloudinary and store the url of the image in my mongoDB database in Node.js App

I am currently storing images using multer to uploads folder in public and then to mongodb using buffer and converting back to 64bit to display it which I think its making website slow.
I know I'm new to all this , really need a step by step procedure and explanation. Thank you
App.js->
const blogSchema=new mongoose.Schema({
name:String,
title:String,
image:String,
body:String,
uploadImg:{
data: Buffer,
contentType: String
},
created:{
//here we define a default date in case the user doesnt type one
type:Date,default:Date.now
}})
multer and storage->
function checkFileType(file,cb){
//allowed extensions
//console.log(file);
const filetypes=/jpeg|jpg|png|gif/;
const extname=filetypes.test(path.extname(file.originalname).toLowerCase());
const mimetype=filetypes.test(file.mimetype);
if(mimetype && extname){
return cb(null,true)
}else{
//console.log(file);
cb('Error : Only Images Accepted');
}
}
var Storage = multer.diskStorage({
destination:"./public/uploads/",
filename:(req, file, callback)=>{
callback(null, file.fieldname+"-"+Date.now()+path.extname(file.originalname))
},
})
var upload = multer({storage:Storage,
limits:{fileSize:10000000},
fileFilter:function(req,file,cb){
checkFileType(file,cb)},
}
).single('file');
in post route->
var obj = {
name:req.body.name,
title:req.body.title,
image:req.body.image,
body:req.body.body,
uploadImg: {
data: fs.readFileSync(path.join( './public/uploads/' + req.file.filename)),
contentType: 'image/png'} }
Blog.create((obj),
in show.ejs->
<img src="data:blog/<%=blog.uploadImg.contentType%>;base64,<%=blog.uploadImg.data.toString('base64')%>" alt="someImage">

NODE GridFStorage with additional data from request doesn't always show up

Hey guys i'm confronting a very strange behaviour with GridFs while i try to upload a file:
So i send with formdata my file which i want to upload and a code which will be set in metadata in files,
the files are saved correctly and the originalname field is always added to the metadata but the code field which is a req.body paramater has a very strange behaviour.
files.ts
uploadFileFormSubmit(event) {
const formData = new FormData();
formData.append('file', event.target.files.item(0));
formData.append('code', this.courseCode);
this.fileService.uploadFile(formData).subscribe(res => ....
fileService.ts
uploadFile(data): Observable<GeneralResponse> {
return this.http.post<GeneralResponse>('/files/uploadFile', data);
}
here is the backend part:
files.js (back-end)
const storage = new GridFsStorage({
url: dbUrl,
file: (req, file) => {
return new Promise((resolve, reject) => {
crypto.randomBytes(16, (err, buf) => {
if (err) {
return reject(err);
}
const filename = buf.toString('hex') + path.extname(file.originalname);
console.log(req.body)
const code = JSON.parse(JSON.stringify(req.body));
console.log(code)
const fileInfo = {
filename: filename,
metadata: {
originalname: file.originalname,
materialCode: code.code
},
bucketName: 'files'
};
resolve(fileInfo);
});
});
}
});
As you can see i parse the req.body in order to get my property (i found it here this solution because the req.body was [Object: null prototype] { code: 'myCode'} )
And for some files this code data is passed but not always.
note that there are 2 console.logs(before and after JSON.parse()
the first object null is an excel file,
the second is a pdf
the third is jpg file
the fourth is a png file
maybe it's something with the extensions but i cannot imagine why the req.body sometimes gets parsed
and the code gets into metadata but other times not :/
So what can cause this behaviour? thanks for help in advance :D

Getting the file in busboy to upload to firebase storage

I am using busboy in node to upload a file to the firebase storage.
But everytme i send the post request with a file, it says..
{
"e": {
"code_": "storage/invalid-argument",
"message_": "Firebase Storage: Invalid argument in `put` at index 0: Expected Blob or File.",
"serverResponse_": null,
"name_": "FirebaseError"
}
}
I can't figure out how do i send the file using busboy..
My code snippet is..
export const uploadAvatar = (req: any, res: any) => {
const busboy = new BusBoy({ headers: req.headers });
let filepath: any;
let imgToBeUploaded: any;
busboy.on(
'file',
(
fieldname: string,
file: any,
filename: string,
encoding: string,
mimetype: string
) => {
if (mimetype !== 'image/jpeg' && mimetype !== 'image/png') {
res.status(400).json({
error: 'Wrong image format',
});
}
const imgExt: string = filename.split('.')[
filename.split('.').length - 1
];
const imgName = `avatar_${req.user.userName}.${imgExt}`;
filepath = path.join(os.tmpdir(), imgName);
// modifiedUrl = `avatar_${req.user.userName}_200x200.${imgExt}`;
file.pipe(fs.createWriteStream(filepath));
imgToBeUploaded = { filepath, mimetype, file };
}
);
busboy.on('finish', async () => {
const storageRef = storage.ref();
try {
const uploadTask = await storageRef.put(imgToBeUploaded.filepath);
console.log(`UploadTask : ${uploadTask}`);
return res.json('File uploaded');
} catch (e) {
return res.status(400).json({ e });
}
});
busboy.end(req.rawBody);
};
The console.log of 'file' returns the location in tempdir, where the file is stored...
Please help me figure out how do i get busboy to return the file, which i can pass to the storageRef.put() 's argument.
For anyone who's here looking for an answer... I've had this problem for over a week now. Kept getting Error: TypeError: Cannot read properties of undefined (reading 'byteLength'). Try doing
storageRef.put(fs.readFileSync(imgToBeUploaded.filepath))
It will actually read the data into a buffer from the temporary file on your local computer and send it on to firestore.
Also it might help to console.log your fs.statSync(imgToBeUploaded.filepath) to make sure the file is actually written. Check the size to make sure it's in the expected range for your image.

multer and node/express file upload not working with Angular

I'm trying to do single file upload using multer. Although I'm able to upload a file manually in the relevant folder using tools like postman for testing the express route, I cannot upload it using Angular frontend. I created a folder called uploads in the node backend folder where the files are supposed to get uploaded. Also I need to upload the file within a form and pass it to a api where it should take the file along with other parameters also. But unfortunately, it is returning status 500 with Internal Server Error on the browser console while on my node terminal it is returning Cannot read property 'path' of undefined.
My node backend code which is working fine is below:
const multer = require('multer')
const storage = multer.diskStorage({
destination: function(req, file, cb) {
cb(null, './uploads/')
},
filename: function(req, file, cb) {
cb(null, Date.now() + file.originalname)
}
})
const upload = multer({storage: storage})
let baseUrl = appConfig.apiVersion+'/blogs';
app.post(baseUrl+'/create', upload.single('imagePath'), (req, res) => {
var today = time.now()
let blogId = shortid.generate()
let newBlog = new BlogModel({
blogId: blogId,
title: req.body.title,
description: req.body.description,
bodyHtml: req.body.blogBody,
isPublished: true,
category: req.body.category,
author: req.body.fullName,
created: today,
lastModified: today,
imagePath: req.file.path //node console is pointing towards this point
}) // end new blog model
let tags = (req.body.tags != undefined && req.body.tags != null && req.body.tags != '') ? req.body.tags.split(',') : []
newBlog.tags = tags
newBlog.save((err, result) => {
if (err) {
console.log(err)
res.send(err)
} else {
res.send(result)
}
}) // end new blog save
});
Below is my Angular Component code which is not working:
selectImage(event) {
if(event.target.files.length > 0){
const file = event.target.files[0];
this.images = file;
}
}
public createBlog(): any {
const formData = new FormData();
const form = formData.append('imagePath', this.images);
let blogData = {
title: this.blogTitle,
description: this.blogDescription,
blogBody: this.blogBodyHtml,
category: this.blogCategory,
imagePath: form
} //end blogData
console.log(blogData);
this.blogHttpService.createBlog(blogData).subscribe(
data => {
console.log(data);
this.toastr.successToastr('Blog Posted Susseccfully!', 'Success!');
setTimeout(() =>{
this.router.navigate(['blog', data.blogId]);
}, 1000)
},
error => {
console.log(error);
console.log(error.errorMessage);
this.toastr.errorToastr('This is not good!', 'Oops!');
})
}
Angular Service code
public createBlog(blogData): any {
let myResponse = this._http.post(this.baseUrl + '/create', blogData);
return myResponse;
}
Frontend HTML Code:
<div>
<input type="file" name="imagePath" (change)="selectImage($event)" />
</div>
It seems like you created a formData object, but you are not actually doing anything with it. As you can see here, you are building up an object and sending it along with your request, but it does not include your formData
let blogData = {
title: this.blogTitle,
description: this.blogDescription,
blogBody: this.blogBodyHtml,
category: this.blogCategory,
imagePath: this.imagePath
} //end blogData
console.log(blogData);
this.blogHttpService.createBlog(blogData).subscribe(
Not entirely sure what the exact syntax would be in your case, but here you can see some sample code I have in a project of mine which will hopefully give you an idea.
changeHandler(e) {
const fd = new FormData();
fd.append('sample', e.target.files[0]);
axios.post('/save-image', fd).then(i => {
this.setState({img: i.data.filename});
});
}
As you can see, the formData is what I am actually sending to the server.

Resources