I'm studying node.js for a school project and I can't figure out why my code won't work. Whenever I upload a form that contains text and a file, the req.body gets populated but the req.files doesn't
server.js
const multer = require('multer')
const bparser = require('body-parser')
app.use(bparser.urlencoded(settings.body_parser))
...
let multer_storage = multer.diskStorage({
destination: (req, file, cb) => {
cb(null, path.join(__dirname, settings.multer.destination))
},
filename: (req, file, cb) => {
cb(null, Date.now() + path.extname(file.originalname))
}
})
let multer_options = {
storage: multer_storage,
fileFilter: (req, file, cb) => {
if (settings.multer.allowed_files.indexOf(file.mimetype) >= 0)
cb(null, true)
cb(null, false)
}
}
app.use(multer(multer_options).any())
app.use("*", (req, res, next) => {
if (!req.session.user)
if (req.cookies.user)
req.session.user = req.cookies.user
next()
})
for (let i = 0; i < settings.routes.length; i++) {
app.use('/', require("./core/routers/" + settings.routes[i]))
}
...
./core/routers/post.js
const router = require('express').Router()
...
router.post('/post/share/', (req, res) => {
let data = {
title: req.body.title,
user: req.session.user,
post: req.files[0].path,
tags: req.tags.split(" ")
}
post.create(data).then((result) => {
return result
})
})
I keep encountering a "TypeError: Cannot read property 'path' of undefined"
When you call cb(null, false) in your fileFilter method, you tell multer that it shouldn't process the file, but it will still enter your middleware with: req.files being undefined that's why you get that error.
If you don't want it to enter to your middleware if the file wasn't processed, then you should pass an error to the callback instead:
let multer_options = {
storage: multer_storage,
fileFilter: (req, file, cb) => {
if (settings.multer.allowed_files.indexOf(file.mimetype) >= 0)
return cb(null, true); // this return is missing
cb(new Error('Invalid file'));
}
}
In any case, you're missing a return statement before cb(null, true); otherwise you're calling twice the callback, once with true and the other once with false
To sum up, if you don't pass an Error to the fileFilter function, you should check for the presence of req.files in your middleware.
Or you can try the code below:
var tmp_path = req.file.path;
Related
I need to write test cases for below method using Jest Framework.
How to write test cases for below method.
I have tried to write test case but getting error Cannot read property 'send' of undefined.
async function uploadFile(req, res) {
let storage = multer.diskStorage({
destination(req, file, cb) {
cb(null, os.tmpdir());
},
filename(req, file, cb) {
let fileExtension = path.extname(file.originalname);
let filename = `${file.fieldname}-${uuidv4()}${fileExtension}`;
req.body.filepath = `${os.tmpdir()}/${filename}`;
cb(null, `${filename}`);
},
});
let multerSingle = multer({
storage: storage,
fileFilter: (req, file, cb) => {
const filetypes = [".xlsx", ".xls", ".xlsb"];
const extname = path.extname(file.originalname).toLowerCase();
if (filetypes.includes(extname)) cb(null, true);
else cb(null, false);
},
}).single("pcr");
multerSingle(req, res, (error) => {
if (error) {
return res.send("Error uploading file.");
}
});
return;
}
I have read documentation for multer. But the current set up I have in my code is different and this makes difficult for me to understand how to handle errors.
It is important now because it happened (only once) that a file was not stored on the server but the code continued saving info in database as if the storing of the file had worked. But it probably did not.
const multer = require('multer');
var docPath = "path_to_disk_where_to_store_files";
var storage = multer.diskStorage({
inMemory: true,
destination: function (request, file, callback) {
callback(null, docPath);
},
filename: function (request, file, callback) {
//Just a function that creates an unique name with timestamp
renamedFile = helpers.createUniqueName(file.originalname);
callback(null, renamedFile);
}
});
var fileFilter = function (req, file, cb) {
var path = require('path');
var ext = path.extname(file.originalname);
if (file.mimetype !== 'application/pdf' || ext.toLowerCase() != '.pdf') {
req.fileValidationError = 'goes wrong on the mimetype';
return cb(null, false, new Error('goes wrong on the mimetype'));
}
cb(null, true);
};
const multerUploader = multer({storage: storage, fileFilter: fileFilter, limits: { fileSize: maxSize }});
router.post('/save_document',[multerUploader.single('file'),saveDocumentInDb]);
I dont really understand where the if-statement that will check if the upload got an error would fit.
Please refer the following for error handling when using multer:
https://github.com/expressjs/multer#error-handling
Your implementation will be something like this:
const multerUploader = multer({storage: storage, fileFilter: fileFilter, limits: { fileSize: maxSize }});
const upload = multerUploader.single('file');
router.post('/save_document', function (req, res) {
upload(req, res, function (err) {
if (err instanceof multer.MulterError) {
// A Multer error occurred when uploading.
} else if (err) {
// An unknown error occurred when uploading.
}
// Everything went fine and save document in DB here.
})
})
I'm using multer with sharp and a custom storage, image upload is set and it works fine but I can not handle the errors correctly.
It is crashing my server when I upload for example a wrong file type or when a file is too big.
on my app.js
const upload = multer({
storage: new customStorage({
destination: function(req, file, cb) {
cb(
null,
path.join(
__dirname,
'/images',
new Date().toISOString().replace(/:/g, '-') +
'-' +
file.originalname.replace(/\s+/g, '-')
)
);
}
}),
limits: { fileSize: 5000000 }
});
on my customStorage.js
const fs = require('fs');
const sharp = require('sharp');
const nodePath = require('path');
function getDestination(req, file, cb) {
cb(null, 'images');
}
function customStorage(opts) {
this.getDestination = opts.destination || getDestination;
}
customStorage.prototype._handleFile = function _handleFile(req, file, cb) {
this.getDestination(req, file, function(err, path) {
if (err) return cb(err);//***the problem is here.***
const outStream = fs.createWriteStream(path);
const transform = sharp().resize(200, 200);
file.stream.pipe(transform).pipe(outStream);
outStream.on('error', cb);
outStream.on('finish', function() {
cb(null, {
path: 'images/' + nodePath.basename(path),
size: outStream.bytesWritten
});
});
});
};
customStorage.prototype._removeFile = function _removeFile(req, file, cb) {
fs.unlink(file.path, cb);
};
module.exports = function(opts) {
return new customStorage(opts);
};
When i upload another file it says:
Error: Input buffer contains unsupported image format
Emitted 'error' event at:
at sharp.pipeline (/Users/David/nodejs-app/node_modules/sharp/lib/output.js:687:18)
I would like to handle the errors with express like this instead.
return res.status(422).render('admin/edit-product', {flash message here.}
That's the way I do it with other errors like when the field is empty.
You can throw the error in your Multer custom storage (which is already being done with cb(err) ), and then catch it in a middleware for express.
const upload = multer({
storage: new customStorage({
destination: function(req, file, cb) {
cb(
null,
path.join(
__dirname,
'/images',
new Date().toISOString().replace(/:/g, '-') +
'-' +
file.originalname.replace(/\s+/g, '-')
)
);
}
}),
limits: { fileSize: 5000000 }
});
var uploadMiddleware = function(req, res, next){
var handler = upload.single('media'); //use whatever makes sense here
handler(req, res, function(err){
//send error response if Multer threw an error
if(err){
res.status(500).render('admin/edit-product', "flash message here.");
}
//move to the next middleware, or to the route after no error was found
next();
});
}
Then use the uploadMiddleware in your express route:
app.post('/route/edit', uploadMiddleware, function (req, res) {
//handle request and render normally
});
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 */
});
I am giving post request /product/create with some value and an image.
if I console every value before
upload(req, res, (err) => {})
it is showing properly with out image info.
if I receive the value after upload(req, res, (err) => {})
No value is showing.
Full post request code:
app.post('/product/create', (req, res) => {
let filename;
upload(req, res, (err) => {
if(err){
res.render('index', {
msg: err
});
} else {
if(req.file == undefined){
res.render('index', {
msg: 'Error: No File Selected!'
});
} else {
res.render('index', {
msg: 'File Uploaded!',
filename = req.file.filename;
});
}
}
});
const product = {
title : req.body.title,
desc : req.body.desc,
image : filename,
}
});
configuring Multer:
const storage = multer.diskStorage({
destination: './public/uploads/',
filename: function(req, file, cb){
cb(null,file.fieldname + '-' + Date.now() + path.extname(file.originalname));
}
});
const upload = multer({
storage: storage,
limits:{fileSize: 1000000},
fileFilter: function(req, file, cb){
checkFileType(file, cb);
}
}).single('myImage');
function checkFileType(file, cb){
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 {
cb('Error: Images Only!');
}
}
Multer does not support 'req.file.filename' outside upload function. As filename, originalname, fieldname etc is inbuild API of multer. It is limited to upload function only.
Now, if you are trying to upload product values inside database then you have to create an insert function inside multer upload function only.