validate request body before uploading a file using multer in nodejs - node.js

I want to upload user profiles according to their school ids
// import multer
var multer = require('multer')
// import school and student models
const { School } = require('./models/school/school.model.js')
const { Student } = require('./models/student/student.model.js')
// configure multer
const storage = multer.diskStorage({
destination: (req, file, cb) => {
const dir = `./uploads/schools/${req.body.schoolId}/students`
fs.exists(dir, exist => {
if (!exist) {
return fs.mkdir(dir, { recursive: true }, error => cb(error, dir))
}
return cb(null, dir)
})
},
filename: (req, file, cb) => {
cb(null, `student-${normaliseDate(new Date().toISOString())}.${file.originalname.split('.')[file.originalname.split('.').length - 1]}`)
}
})
var upload = multer({storage: storage}).single('profile')
app.post('/student', function (req, res) {
// check if submitted school id is valid
let school = await School.findOne({ _id: req.body.schoolId })
if (!school)
return res.send(`School of id ${req.body.schoolId} doesn't exist`)
// upload the profile pic
upload(req, res, function (err) {
if (err)
return res.send(err)
})
// save the student
let newStudent = new Student({
name: req.body.name,
email: req.body.email,
school: req.body.schoolId,
profile: req.file.filename
})
const saveDocument = await newStudent.save()
if (saveDocument)
return res.send(saveDocument).status(201)
return res.send('New College not Registered').status(500)
})
but when I try to access req.body before uploading the profile I get an empty object.
I would upload the profiles in a tempolary folder and move them later but what if the submitted school id is invalid ?? this will require me to delete the uploaded file. so I want to make sure the submitted details are valid and then upload the profile later save the student.

Check your codes, you are using await but async is nowhere to be found. Though it may not be the cause of getting an empty req.body object.
let school = await School.findOne({ _id: req.body.schoolId })
const saveDocument = await newStudent.save()

I am not sure of how you are sending data to the backend but try these option it may work
remember that when javascript is sending POST data from client to server it sends them according to how they are arranged so if you have your client form like this
<form action="">
<input type="file" name="pic">
<input type="text" name="username">
<input type="number" name="age">
</form>
it will send file named pic first, then text named username secondly and finally send number named age, so if you are getting empty req.body while the file has not been completely uploaded you might check this if it is the problem because javascript process them on an order according to how they were sent
i hope this gonna help

Related

How to get Original File From Create URL.createObjectURL to save the file in Specific Location and save path in Mongo DB

I need to add Product along with details and Image from react. Issue i am facing is how to append the file to form Data after the file is being uploaded
The process I tried is declared formData at file level and on image change i created
formData.append('productImage',event.target.files[0])
And on button submit when i am checking if formData has that productImage it says not there and also on node JS end, when i am printing req.file => it is showing log is undefined.
Any way I can get the file to save it in the required path
DB : Mongo
Product:{name : String , Image: String}
Node JS Code :
I am Multer and specified Path where this file needs to be saved
router.post(
"/admin/products",
upload.single("productImage"),
async (req,res) => {
const {name,category,price,discountPrice,description,isTopProduct} = req.body;
console.log('Input Request');
console.log(req.file);
var quantityAvailable= 1;
var imageURL = "";
if(Object.keys(req).includes("file") && req.file.originalname){
imageURL = "./images/products/"+req.file.originalname
}
if(Object.keys(req.body).includes("quantityAvailable")){
quantityAvailable = req.body.quantityAvailable;
}
try{
let product = new Product({
name:name,
category:category,
price:price,
discountPrice:discountPrice,
description:description,
isTopProduct:isTopProduct,
image:imageURL,
quantityAvailable:quantityAvailable
});
await product.save();
return res.status(200).json({status:"success",message:"Product Created Successfully"})
}
catch(e)
{
console.log(e.message);
return res.status(500).send({message:"Error in Saving Product"});
}
});
React Code:
async function addProduct(){
if(validateInput()){
if(formData.has('productImage')){
console.log('has Product Image');
}
formData.append('name',productName);
formData.append('category',category);
formData.append('price',parseInt(price));
formData.append('discountPrice',discountPrice);
formData.append('description',productDescription);
formData.append('isTopProduct',isTopProduct);
await axios.post('http://localhost:8080/api/v1/admin/products'
, formData)
.then(response=>{
console.log(response);
if(response.status===200){
setSuccessMsg('Product had been Created Successfully');
setProductDescription('');setPrice('');setProductName('');
setIsTopProduct(false);setIsProductImage('../images/products/NI-Placeholder.png');
}
})
}
}
function handleImageChange(event){
if(event.target.files[0]){
if(formData.has('productImage')){
formData.delete('productImage');
formData.append('productImage',event.target.files[0]);
console.log('Adding Product Image to FormData if');
}
else{
console.log('Adding Product Image to FormData ELse');
formData.append('productImage',event.target.files[0]);}
var createupdatedURL = URL.createObjectURL(event.target.files[0]);
setIsProductImage(createupdatedURL);
}
}
<div className='product-image'>
<label>Product Image</label>
<input type="file" multiple accept="image/*" onChange={handleImageChange}></input>
</div>
<button className='addProductButton' onClick={addProduct}>Add Product</button>

req.file is undefined - multer and nodejs

All the data is being sent to the backend except for the image file. I keep getting req.file is undefined, which prevents data from being stored in the database.
On the server side I have a routes folder with the that handles a new food entry from the user.
const multer = require('multer')
var fs = require('fs')
var storage = multer.diskStorage({
destination: function(req, file, cb) {
cb(null, 'uploads')
},
filename: function(req, file, cb) {
cb(null, file.fieldname + '-' + Date.now())
}
})
var upload = multer({storage: storage})
router.route('/add', upload.single('foodImage')).post((req, res) => {
var img = fs.readFileSync(req.body.file.path);
var encode_image = img.toString('base64')
var finalImg = {
contentType: req.file.mimetype,
image: new Buffer(encode_image, 'base64')
}
console.log(finalImg);
// remaining code left out
On the front end I have a form that collects input from user that includes a photo and a text description. This is in a component file.
fileSelectedHandler(e) {
this.setState({
selectedFile: e.target.files[0]
})
}
onSubmit(e) {
const nutrition = {
'calories': calories,
'fat': fat,
'sugar': sugar,
'carbs': carbs,
'cholesterol': cholesterol,
'protein': protein,
'photo': result.foods[0].photo.thumb,
'date': this.state.date,
'description': this.state.description,
'food_list': food_list.toString(),
'foodImage': this.state.selectedFile
}
console.log(nutrition);
axios.post('http://localhost:5000/nutrition/add', nutrition).then(res => console.log(res));
window.location = '/nutrition';
//remaining code hidden
render() {
return (
<div>
<h3>Create New Nutrition Log</h3>
<form onSubmit={this.onSubmit} encType='multipart/form-data'>
<div className="form-group">
<label>Upload food image!</label><br></br>
<input type="file" onChange={this.fileSelectedHandler} name="foodImage"/>
</div>
<div className="form-group">
<label>Description: </label>
<input type="text"
required
className="form-control"
value={this.state.description}
onChange={this.onChangeDescription}
/>
</div>
//remaining code hidden
. Client Side
To send images from client to the server your request data should have a multipart/form-data structure. You can accomplished that with:
const data = new FormData();
data.append('calories', calories)
data.append('fat', fat)
data.append('sugar', sugar)
data.append('carbs', carbs)
data.append('cholesterol', cholesterol)
data.append('protein', protein)
data.append('photo', result.foods[0].photo.thumb)
data.append('date', this.state.date)
data.append('description', this.state.description)
data.append('food_list', food_list.toString())
data.append('foodImage', this.state.selectedFile)
axios.post('http://localhost:5000/nutrition/add', data)
.then(res => console.log(res));
Also, this part of the code where you set encType has no use because you are doing a custom post with axios, not directly from form. (You can remove encType).
<form onSubmit={this.onSubmit} encType='multipart/form-data'>
. Server Side
And to get your file from server side:
// BEFORE
// router.route('/add', upload.single('foodImage')).post((req, res) => {
// AFTER
router.route("/add").post(upload.single("foodImage"), (req, res) => {
// You can get image details (path, fieldname, size, etc) from request.file.
console.log(req.file);
// And JSON data, you can get it normally from request.body
console.log(req.body);
Also take a look at your post path, on server you are setting it as '/add' and on client you are sending the request to 'nutrition/add'
Finally, there is this link that could be useful to you.
rest-api-file-ie-images-processing-best-practices

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.

Angular6 Rename the File to be Uploaded

I am using Angular6 and NodeJS for my API. MY application is a School Software where I should save Students Images with their Registration Numbers. I am using ng2-file-upload in Angular for File Uploads and Multer in NodeJS, Upload is working perfectly, but I can't understand how to rename the file with the registration number of Student. My url in Angular consists of Student Registration number, I just need to know how to send that Reg. Number to NodeJS and rename the file with Reg. Number
My html File
<input type="file" name="photo" ng2FileSelect [uploader]="uploader" />
<button type="button" class="btn btn-success btn-s"
(click)="uploader.uploadAll()"
[disabled]="!uploader.getNotUploadedItems().length" >
Upload an Image
</button>
My .ts file
public uploader: FileUploader = new FileUploader({url: URL, itemAlias: 'file'});
ngOnInit() {
this.uploader.onAfterAddingFile = (file) => { file.withCredentials = false; };
this.uploader.onCompleteItem = (item: any, response: any, status: any, headers: any) => {
console.log('ImageUpload:uploaded:', item, status, response);
alert('File uploaded successfully');
};
}
My NodeJS Multer Upload:
var storage = multer.diskStorage({
destination: function (req, file, cb) {
cb(null, 'E:/school')
},
filename: function (req, file, cb) {
cb(null, Date.now() + '-' + file.originalname);
}
});
var upload = multer({ storage: storage });
router.post('/upload', upload.single('file'), (req, res) => {
if (!req.file) {
console.log("No file received");
return res.send({
success: false
});
} else {
console.log('file received');
return res.send({
success: true
})
}
});
Thanks in Advance. Any help will be Appreciated.
You can edit the name before posting the formData to the backend. Give the file name (or get from a form) and get the file extension from the file to upload. Then append to formData before posting to the backend.
Reference: https://stackoverflow.com/a/55178342 and https://youtu.be/v67NunIp5w8
let fileToUpload = <File>files[0];
let fileName:string = 'test-name' //get name from form for example
let fileExtension:string = fileToUpload.name.split('?')[0].split('.').pop();
const formData = new FormData();
formData.append('file', fileToUpload, fileName+'.'+fileExtension);
this.http.post(environment.backendApiUrl+'/api/upload', formData, {reportProgress: true, observe: 'events'})
.subscribe(event => {
if(event.type === HttpEventType.UploadProgress) {
this.progress = Math.round(100 * event.loaded / event.total);
} else if (event.type === HttpEventType.Response) {
this.message = 'Upload success.';
this.onUploadFinished.emit(event.body);
}
});
Be careful: This solution should work however this code is ugly and probably not the best way to do what you want. But it's just to show you how you can rename your file in the front-end side.
Important: I will assume you are uploading a single file because we will use the first item of the uploader.queue for this example.
First, you can add an input type text in your HTML file just below your input file :
<input *ngIf="uploader.queue.length" type="text" name="file" [value]="uploader.queue[0].file.name"
(change)="renameFile($event.target.value)"/>
This input will be showed when you will upload a file. The (change) will trigger the renameFile func in your component with the current input value.
Then update your TS component by adding the renameFile method :
renameFile(name: string): void {
// fetching the item uploaded
const oldFileItem: FileItem = this.uploader.queue[0];
// re-instanciating a new file based on the old file by changing the name property
const newFile: File = new File([this.uploader.queue[0]._file], name, {type: oldFileItem.file.type});
// uploader.queue is taking FileItem objects, instanciating a new FileItem
const newFileItem = new FileItem(this.uploader, newFile, this.opts);
// replacing the old one by the new file updated
this.uploader.queue[0] = newFileItem;
}
Finally, you can have a look to the file property in your console.log inside the onCompleteItem function, the file has been updated.
You should be able to watch the onFileSelected event and get file[0] (if you are single file upload).
And set the file[0].name before uploading.

Using Node, Express and Multer for Upload a file to MongoDB? What should I use, Multer or GRIDfs?

Hello I'm running a final project on heroku using MongoLab and Multer to upload photos seems to work fine but after a few hours or less the photos dissapear from the page but leaves the img placeholder and another field like name or date area where it was.
Is this Heroku or Mongolab issue or is it just the free account is this because of the free acounts. also it doesnt matter if there is one photo uploaded or a dozen they will still dissapear after a while.
Could you help me? Is there a hack, tricky line of code or something that I made wrong with Multer or my database? I have made a deep research in the web and Stack-Overflow I found something call GRIDfs but I don't know if that will work. Could you tell me if I'm in rigth way?
This is my code for Multer:
var express = require("express"),
body_parser = require("body-parser"),
multer = require("multer"),
mongoose = require("mongoose");
var app = express();
//connect my local database to MongoLab database
var url = process.env.DATABASE || "mongodb://localhost/black_hat";
mongoose.connect(url);
Multer configuration
var storage = multer.diskStorage({
destination: function(req, file, callback){
callback(null, "./public/uploads");
},
filename: function(req, file, callback){
callback(null, Date.now() + file.originalname);
}
});
var upload = multer({storage : storage}).single("image");
My POST route
app.post("/fotos_videos", isLoggedIn, function(req, res) {
upload(req, res, function(err) {
if(err){
return res.end("Error uploading file");
}
// get data from form
var newName = req.body.name,
newCover = "/uploads/" + req.file.filename;
// Packing into an object
var newCollage = {
name : newName,
cover: newCover
};
//create new album
Collage.create(newCollage, function(err, collage) {
if(err){
console.log(err);
} else {
// console.log(collage);
res.redirect("/fotos_videos");
}
});
});
});
The form where I upload the picture
<div style="width=30%; margin: 30px auto" >
<form action="/fotos_videos" method="POST" enctype="multipart/form-data">
<div class="form-group">
<label>Album Name</label>
<input type="text" name="name" placeholder="Album Name" class="form-control">
</div>
<div class="form-group">
<label>Choose Album Cover</label>
<input type="file" name="image" placeholder="image url" class="form-control" required>
</div>
<div class="form-group">
<button class="btn btn-lg btn-primary btn-block">Upload</button>
</div>
</form>
Back
</div>
Finally my Model for Collage
var mongoose = require("mongoose");
// Schema
var collageSchema = new mongoose.Schema({
name : String,
cover : String,
photos: [{
type: mongoose.Schema.Types.ObjectId,
ref: "Photo"
}]
});
var Collage = mongoose.model("Collage", collageSchema);
module.exports = Collage;
Any kind of help will be appreciated.
if you want to save the image to the db u should not have "dest" property. also implementation of multer is wrong maybe because it is an old question and previous api was like that. here what u should u do.
configure your multer:
const multer = require("multer");
const upload = multer({
limits: { fileSize: 5000000 },
fileFilter(req, file, cb) {
if (!file.originalname.match(/\.(jpg|jpeg|png)$/)) { //allowed file extensions
return cb(new Error("please upload png,jpeg or jpg"));
}
cb(undefined, true);
}
});
then create an endpoint. u already have this
app.post("/fotos_videos", isLoggedIn, function(req, res) {
upload(req, res, function(err) {
this iplementation wrong. upload is a middleware and if you wanna add authentication, only authenticated user should be access to this endpoint. so we need to tweak this endpoint.
Also u need to define a schema for uploads in order to save them to the db. let's say u created a model named uploads and this to it. we do not need to validate it by mongoose because multer already takes care of it. make sure u delete the dest property from multer configuration. because if u dont delete, multer will save the file to the folder so our async (req,res){} function will not be able to access to the file so we cannot save it to the db.
uploads:{type:Buffer} //include this in your uploads schema
then in this file call Uploads model.
router.post(
"/fotos_videos/me/uploads", isLoggedIn,
upload.single("uploads"), //uploads is just a random name when u upload from postman, form form-data,put "uploads" in key field
async (req, res) => {
req.upload.uploads = req.file.buffer;//i explain down below
await req.upload.save();
res.send();
},
(err, req, res) => {
res.status(400).send("error happned");
}
);
We do not store our pictures in the filesystem. The reason is almost all the deployment platforms, require us to take our code and push it up to the repository on their server. So the file system gets wiped every time we deploy which means we will lose data when we deploy. So instead of storing the images on the file system, we are going to add a field to the user model to store the image of binary data in the database.
now it is time to fetch our image from the db. for this u need to have a new route
router.get("/fotos_videos/:id/uploads", async (req, res) => {
try {
const upload = await Uploads.findById(req.params.id);
if (!upload || !upload.uploads) {
throw new Error("no image found");
}
res.set("Content-Type", "image/png");
res.send(upload.uploads);
} catch (e) {
res.status(400).send(e.message);
}
});
I don't know if this will be helpful. but from my understanding multer is the stuff used to get the data from the user (like uploaded images). It doesn't do persistent storage. If you want to store the data (large files like images) you should use gridfs-stream .
You should a create a read stream to GridFs once the data comes in from the user.

Resources