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.
Related
I'm having an issue with file uploading with Multer and unfortunately, I don't have a lot of experience with Multer.
In this project, I'm trying to structure things such that routes call functions (controllers) which run commands, for example creating a product etc.
I'm pretty sure I set up Multer correctly, however when I try to req.file.filename in the controller it returns an undefined.
This is my setup (currently Multer is a helper function, I'm going to move it to middleware after as this is incorrect).
File Storage Helper Func
const multer = require("multer");
//SET Storage
let storage = multer.diskStorage({
destination: function (req, file, cb) {
cb(null, "public/uploads");
},
filename: function (req, file, cb) {
const fileName = file.originalname.replace(" ", "-");
cb(null, fileName + "-" + Date.now());
},
});
const uploadOptions = multer({ storage });
module.exports = { uploadOptions };
Product Router
const express = require("express");
const router = express.Router();
//#Helpers
const { uploadOptions } = require("./../../Helpers/FileStorage.helper");
//#Categories
const {
getProducts,
postProduct
} = require("./../../Controllers/Products/product.controller");
//GET ALL Products + ADD NEW Product
router
.get("/", getProducts)
.post("/", uploadOptions.single("image"), postProduct);
And finally, the post product controller:
//POST A New Product
const postProduct = expressAsyncHandler(async (req, res) => {
const category = await Categories.findOne(req.body.category);
if (!category) return res.status(400).send("Category is invalid, try again!");
const fileName = req.file.filename;
const basePath = `${req.protocol}://${req.get("host")}/public/upload/`;
const product = new Product({
name: req.body.name,
description: req.body.description,
richDescription: req.body.richDescription,
image: `${basePath}${fileName}`,
brand: req.body.brand,
price: req.body.price,
category,
countInStock: req.body.countInStock,
rating: req.body.rating,
numReviews: req.body.numReviews,
isFeatured: req.body.isFeatured,
});
const productList = await Product.findOne({ name: product.name });
if (productList != null) {
return res.status(404).send("Product already exists! Please try again!");
}
try {
await product.save();
res.status(200).send(product);
} catch (e) {
res.status(500).send("Product was not created! Error: " + e.message);
}
});
I know the traditional way of doing this in routes would be the following (which works!):
router.post("/", uploadOptions.single("image"), async(req,res) => {
//Run function
}
However, as I mentioned above, I'm trying to break the route actions up into controller functions.
When console.log(req.file) it returns an undefined.
I suspect the props aren't being passed to the postProduct function which is what causes the error, but I can't figure out how to resolve this. I've been staring at this too long, perhaps it's an easy thing to resolve and I'm being stupid (highly probable).
If someone could assist me in fixing this and explain where I'm going wrong, I would be eternally grateful.
Edit: This is the ERROR: " TypeError: Cannot read property 'filename' of undefined "
This statement: return console.log(req.file) in postProduct prevents the rest of the code from running and returns void. Console.log always returns void.
If you want to just log the file and continue with the rest of the function, remove the return keyword: console.log(req.file).
What does the console.log output in the terminal when you run the app?
I dont think that you are parsing the form data correctly:
// this is for parsing json data
app.use(express.json());
// this is for parsing form data
app.use(express.urlencoded({ extended: false }));
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],
}
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
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">
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.