I can't figure out a simple thing. If I do pass files with the request, I want to save them, and then modify req.body a little bit inside the same multer middleware. My multer middleware:
const storage = multer.diskStorage({
destination: (req, file, cb) => {
cb(null, './uploads/')
},
filename: (req, file, cb) => {
cb(null, req.body._id + path.extname(file.originalname))
},
})
const fileFilter = (req: Request, file: Express.Multer.File, cb: multer.FileFilterCallback) => {
if (
file.mimetype === 'audio/wave' ||
file.mimetype === 'image/png' ||
file.mimetype === 'image/jpeg'
)
return cb(null, true)
cb(null, false)
}
const upload = multer({
storage: storage,
limits: {
fileSize: 1024 * 1024 * 3, // up to 3 megabytes
},
fileFilter: fileFilter,
})
export const saveFiles = upload.fields([
{ name: 'audio', maxCount: 1 },
{ name: 'image', maxCount: 1 },
])
Right now I do it in the router:
if (req.files) {
if ((req as any).files.audio)
req.body.data.audio = (req as any).files.audio[0].path.replace('\\', '/')
if ((req as any).files.image)
req.body.data.image = (req as any).files.image[0].path.replace('\\', '/')
}
Which is kinda annoying, I would like to just do it inside the multer somehow before it fires next(). I just can't figure out how.
So, saveFiles is your middleware function. You don't show where you actually use it, but presumably you are registering it in your router as middleware somewhere. Because it's middleware, that means it is a function that expects to be called with the arguments (req, res, next). You can replace that next argument with your own and do your work in their like this:
// multer's middlware function, we will wrap
const saveFilesMiddleware = upload.fields([
{ name: 'audio', maxCount: 1 },
{ name: 'image', maxCount: 1 },
]);
// wrap the multer middleware with our own
export const saveFiles = function(req, res, next) {
saveFilesMiddleware(req, res, err => {
if (err) {
// upon error, just call the real next with the error
next(err);
} else {
// when no error, do our housekeeping in req.body
if (req.files) {
if ((req as any).files.audio)
req.body.data.audio = (req as any).files.audio[0].path.replace('\\', '/');
if ((req as any).files.image)
req.body.data.image = (req as any).files.image[0].path.replace('\\', '/');
}
next();
}
});
};
Related
I built a web app and want to resize my images to be smaller for better quality to my profile pictures. I am using "multer" for upload picture and sharp package for resizing.
For some reason i get this error:
"[0] [Error: D:\DevConnectors\public\resized\5f4f4e0bb295ba36042536bf.jpg: unable to open for write
[0] windows error: The storage control block address is invalid."
My code:
const storage = multer.diskStorage({
destination: (req, file, cb) => {
cb(null, DIR)
},
filename: (req, file, cb) => {
var typeFile = file.originalname.split(".").pop()
const fileName = req.user.id + "." + typeFile
cb(null, fileName)
},
})
var upload = multer({
storage: storage,
fileFilter: (req, file, cb) => {
if (
file.mimetype == "image/png" ||
file.mimetype == "image/jpg" ||
file.mimetype == "image/jpeg"
) {
cb(null, true)
} else {
return cb(new Error("Only .png, .jpg and .jpeg format allowed!"), false)
}
},
})
const Profile = require("../../moduls/Profile")
const User = require("../../moduls/User")
//#route GET/api/profile/me
//#desc Get current users profile
//#access Private
router.post(
"/upload",
[auth, upload.single("imageProfile")],
async (req, res) => {
try {
console.log(req.file)
const { filename: image } = req.file
await sharp(req.file.path)
.resize(150)
.jpeg({ quality: 50 })
.toFile(path.resolve(req.file.destination, "resized", image))
fs.unlinkSync(req.file.path)
const url = req.protocol + "://" + req.get("host")
let user = await User.findById(req.user.id)
const profile = await Profile.findOne({ user: req.user.id })
//Update
if (user) {
user.avatar = url + "/public/" + req.file.filename
await user.save()
return res.json(profile)
}
} catch (err) {
console.log(err)
}
}
)
This happens at this line :
path.resolve(req.file.destination,'resized',image))
What am i doing wrong? i am using sharp docs.
Try this
Configuration of multer ( change it according to your needs)
import multer from "multer";
import sharpe from "sharp";
const upload = multer({ //multer configuration
//dest: "avatars", //so that buffer is available in route handler
limits: {
fileSize: 1000000,
},
fileFilter(req, file, cb) { // object method shorthand syntax
if (!file.originalname.match(/\.(jpg|jpeg|png)$/)) { //.match for using regex b/w (//)
return cb(new Error("Please upload a IMAGE"));
}
cb(undefined, true);
},
});
handle sharp in your route handle like this.
router.post(
"path",
upload.single("avatar"),
async (req, res) => {
const buffer = await sharpe(req.file.buffer)
.png()
.resize({
width: 300,
height: 300
})
.toBuffer();
req.user.avatar = buffer;
await req.user.save();
res.send();
},
(error, req, res, next) => {
//to tell express this how mutler/s error should be handled
res.status(400).send({
error: error.message,
});
}
);
I had the same problem, and in my case the issue was that the directory (in your case "resized") didn't exist yet. So either create it manually or programmatically like this:
const targetPath = path.resolve(req.file.destination, 'resized')
if (!existsSync(targetPath)) {
mkdirSync(targetPath);
}
await sharp(...)
I use React and Express for create Mutiupload Image
multer.js
const multer = require('multer');
const storage = multer.diskStorage({
destination: function (req, file, cb) {
cb(null, './public/')
},
filename: function (req, file, cb) {
cb(null, new Date().toISOString() + '-' + file.originalname)
}
})
const fileFilter = (req, file, cb) => {
if (file.mimetype === 'image/jpeg' || file.mimetype === 'image/png') {
cb(null, true)
} else {
//reject file
cb({message: 'Unsupported file format'}, false)
}
}
const upload = multer({
storage: storage,
limits: { fileSize: 1024 * 1024 },
fileFilter: fileFilter
})
module.exports = upload;
post.controller.js
async onInsert(req, res) {
try {
let result = await Service.insert(req.body)
res.success(result, 201);
} catch (error) {
res.error(error.message, error.status)
}
},
post.service.js
insert(data) {
return new Promise(async(resolve, reject) => {
try {
const obj = new Post(data)
let inserted = await obj.save()
resolve(inserted)
} catch (error) {
reject(methods.error(error.message, 400))
}
});
},
I try to implements Multer on this controller but it can't upload anyway . so how to implements mutiple upload image with this code thank
I use mongoose by the way
const fileStorage = multer.diskStorage({
destination: (req, file, cb) => {
cb(null, 'images');
},
filename: (req, file, cb) => {
cb(null, new Date().toISOString().replace(/:/g, '-') + file.originalname);
}
});
const fileFilter = (req, file, cb) => {
if (
file.mimetype === 'image/png' ||
file.mimetype === 'image/jpg' ||
file.mimetype === 'image/jpeg'
) {
cb(null, true);
} else {
cb(null, false);
}
};
const csrfProtection = csrf();
app.use(bodyParser.urlencoded({ extended: false }));
app.use(
multer({ storage: fileStorage, fileFilter: fileFilter }).fields(
[
{
name: 'image1',
maxCount: 1
},
{
name: 'image',
maxCount: 1
},
{
name: 'passbook',
maxCount: 1
},
{
name:'certificate',
maxCount:1
}
]
)
);
in controller
const image=req.files.image1;
const passbook=req.files.passbook;
const certificate=req.files.passbook;
if (!image || !passbook || !certificate) {
console.log("img err");
return res.redirect('admin/');
}
const imageUrl = image[0].path;
const passbookUrl= passbook[0].path;
const certificateUrl= certificate[0].path;
this works for me but i have only used Nodejs
I am setting static path but getting error : "Error: ENOENT: no such file or directory, open 'C:\dashboard new - Copy\uploads\2019-11-28T08:11:09.164Z1564660431900.jpg'"
const storage = multer.diskStorage({ destination: function(req, file, cb) { let dest = path.join(__dirname, '../../uploads'); cb(null, dest); }, filename: function(req, file, cb) { cb(null, new Date().toISOString() + file.originalname); }});
const fileFilter = (req, file, cb) => { if (file.mimetype === 'image/jpeg' || file.mimetype === 'image/png') { cb(null, true); } else { cb(null, false); }};
const upload = multer({ storage: storage, limits: { fileSize: 1024 * 1024 * 5 }, fileFilter: fileFilter});
router.post("/", upload.single('productImage'), async (req, res, next) => {
try {
cosole.log('hi');
const product = new Product({
_id: new mongoose.Types.ObjectId(),
name: req.body.name,
price: req.body.price,
productImage: req.file.path
});
const saveImage = await product.save();
console.log(saveImage)
res.json(saveImage);
} catch (error) {
console.log(error);
res.json(error);
}
});
How to do this?
I think you need to provide the destination folder as a key and value, something like this(below)
var upload = multer({ dest: 'uploads/' })
You can check out the full multer documentations here
https://expressjs.com/en/resources/middleware/multer.html
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'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;