multer: dynamic destination path - node.js

I am writing a node application and I was looking for something to upload files on the server. I could get files to upload when there was only one static directory. But I need to make directories per user and then upload files to those, according to the user that's logged in. I looked stuff up but everything that I try ends in an Error: ENOENT: no such file or directory, open ... error. What I am trying to do currently is this -
let storage = multer.diskStorage({
destination: function(req, file, cb) {
let dest = path.join(__dirname, './documents', 'somenameigetfromtheuser');
let stat = null;
try {
stat = fs.statSync(dest);
}
catch (err) {
fs.mkdirSync(dest);
}
if (stat && !stat.isDirectory()) {
throw new Error('Directory cannot be created');
}
cb(null, dest);
}
});
let upload = multer({
storage: storage,
dest: 'documents/'
});
app.post('/testUpload', upload.single('testfile'), (req, res) => {
res.json({
test: 'test'
})
});
There is a similar question that has been answered but it doesn't work that way for me because I want the directory name from the request object.
When I remove the storage property in my multer initialization, the files are stored in the documents directory with a random name. I want the file to have its original name and I want it to be stored in a directory where I get the name of the directory from the req object.
Help a brother out, thanks!

edited
See https://github.com/expressjs/multer#diskstorage
Note that req.body might not have been fully populated yet. It depends on the order that the client transmits fields and files to the server.
Due to that, first write file in temp directory, read directory name from req and move file:
fs = require('fs-extra'); //npm install fs.extra
...
var storage = multer.diskStorage({
destination: function (req, file, cb) {
cb(null, '../tempDir/')
},
filename: function (req, file, cb) {
cb(null, file.originalname)
}
});
var upload = multer({
storage: storage
}).single('file');
upload(req, res, function (err) {
if (err) {
res.json({});
return;
}
var dir = JSON.parse(req.body.data).directory;
var filename = req.file.filename;
fs.move('../tempDir/' + fileName, '../tempDir/' + dir + '/' + fileName, function (err) {
if (err) {
return console.error(err);
}
res.json({});
});
});

Here's code for dynamic path by argument!
exports.upload = (folderName) => {
return imageUpload = multer({
storage: multer.diskStorage({
destination: function (req, file, cb) {
const path = `src/assets/uploads/${folderName}/`;
fs.mkdirSync(path, { recursive: true })
cb(null, path);
},
// By default, multer removes file extensions so let's add them back
filename: function (req, file, cb) {
cb(null, Date.now() + path.extname(file.originalname));
}
}),
limits: { fileSize: 10000000 },
fileFilter: function (req, file, cb) {
if (!file.originalname.match(/\.(jpg|JPG|webp|jpeg|JPEG|png|PNG|gif|GIF|jfif|JFIF)$/)) {
req.fileValidationError = 'Only image files are allowed!';
return cb(null, false);
}
cb(null, true);
}
})
}
and call it

Make sure you append first the textfields on the client-side and only then do you append the files. In my case i had something like this:
`
for(let i=0; i<files.length;i++)
{
formData.append("files[]",files[i]);
}
formData.append("username",username);
`
The fix was to first append the textfield like so:
`
formData.append("username",username);
for(let i=0; i<files.length;i++)
{
formData.append("files[]",files[i]);
}
`

Here's what I do for uploading files to dynamic directories.
In frontend I use URL parameters to pass user IDs.
await axios({
method: 'post',
data: formData,
url: '/api/upload?userId=123',
headers: { 'content-type': 'multipart/form-data' }
})
In backend get that parameter and use for destination. Also create the directory if it doesn't exist.
const upload = multer({
storage: multer.diskStorage({
destination: (req, file, cb) => {
const directory = `./public/uploads/${req.query.userId}`
if (!fs.existsSync(directory)) {
fs.mkdirSync(directory, { recursive: true })
}
cb(null, directory)
},
filename: (req, file, cb) => {
cb(null, `${Date.now()}-${file.originalname}`)
}
})
})

In my project I use multer as follow:
1.Store the file first in a common directory, like /tmp/.
2.Copy/move the file anywhere you want, to CDN in my case, and to a user folder in yours.
3.Remove the original file in /tmp if needed.
And maybe let upload = multer({
storage: storage,
dest: 'documents/'
}); you should remove the dest here since you specified dest in storage, right?

const storage = multer.diskStorage({
destination: function(req, file, callback){
callback(null, path.dirname('D:/') + 'Integra Qamba Site/');
},
filename: function(req, file, callback){
let data = new Date();
callback(null, dateTime +".jpg");
}
});

Related

Not able to save image with original name and extension through multer in node.js

Hi I am trying to save an image in my image folder through multer but the issue is that the file is being saved with a randomly generated filename without extension.I have tried to resolve this issue by referring to various articles but still facing the same issue.
referred the following :
Multer is not saving the file as the same name and without extension?
The file gets saved in the image folder like below :
a9bfcba8e950ccfbdaf7f0d2f8d58374
Hence if someone could please help me resolve this issue.
profile.js
const upload = multer({
dest:'images',
filename: function (req, file, cb) {
cb(null, file.originalname)
},
limits: {
fileSize: 10000000,
},
fileFilter(req, file, cb) {
if(!file.originalname.match(/\.(jpg|jpeg|png|JPG|HEIC)$/)) {
return cb(new Error('Please attach an image'))
}
cb(undefined, true);
}
})
router.post('/user/upload', upload.single('profile_pic') , async (req,res) => {
console.log(req.body)
const url = req.protocol + '://' + req.get('host')
//when inserting the file in the database we are able to send the exact location with original file name and extension.
var filepath = url + '/Users/images/';
var reqFiles = (filepath + req.file.filename + path.extname(req.file.originalname)
console.log(req.file);
const notify = new user({
userId: req.body.userId,
profile_pic: reqFiles
})
you need to do step wise process for storing a file.
//storage for image upload
const storage = multer.diskStorage({
destination: './upload',
filename: function (req, file, cb) {
cb(null, file.fieldname + '-' + Date.now() + path.extname(file.originalname));
}
});
//file filter for extention
let fileFilter = function (req, file, cb) {
console.log(file.mimetype)
const allowedMimes = ['image/jpeg', 'image/jpg', 'image/png', 'image/gif'];
if (allowedMimes.includes(file.mimetype)) {
cb(null, true);
} else {
cb(null, false);
}
};
//upload for to pass storage, file size limit and filter
//maximum file size is 10Mb
const upload = multer({
storage: storage,
limits: { fileSize: 10 ** 7 },
fileFilter: fileFilter
}).single('userImage');
then call your route.
router.post('/upload', (req,res)=>{
upload(req, res, (err)=>{
if(err){
console.log(err)
}
else{
console.log(req.file)
console.log(req.file.path)
}
})
})
By following this approach then definitely your error will solved.

How upload files and optimize them before upload using multer - NodeJs

I'm doing a social network using Nodejs, react and mongo. I'm using multer to upload images but I need optimize them before upload at the directory.
Multer
const storage = multer.diskStorage({
destination(req, file, cb) {
cb(null, "./uploads/publications");
},
filename(req, file = {}, cb) {
const { originalname } = file;
const fileExtension = (originalname.match(/\.+[\S]+$/) || [])[0];
crypto.pseudoRandomBytes(16, function (err, raw) {
cb(null, raw.toString("hex") + Date.now() + fileExtension);
});
},
});
var mul_upload = multer({ dest: "./uploads/publications", storage });
Route
app.post(
"/publication",
[md_auth.ensureAuth, mul_upload.single("image")],
PublicationController.savePublication
);
Is it possible to compress and optimize the image before uploading it?
You need to use a npm package. sharp it can be very a very good option.
const sharp = require('sharp')
sharp(req.file).resize(200, 200).toBuffer(function(err, buf) {
if (err) return next(err)
// you can do anything with the buffer
})
With multer you can implement the custom storage function. You can check here how to do it. I am adding the sample code here:
var fs = require('fs')
function getDestination(req, file, cb) {
cb(null, '/dev/null')
}
function MyCustomStorage(opts) {
this.getDestination = (opts.destination || getDestination)
}
MyCustomStorage.prototype._handleFile = function _handleFile(req, file, cb) {
this.getDestination(req, file, function(err, path) {
if (err) return cb(err)
var outStream = fs.createWriteStream(path)
var resizer = sharp().resize(200, 200).png()
file.stream.pipe(resizer).pipe(outStream)
outStream.on('error', cb)
outStream.on('finish', function() {
cb(null, {
path: path,
size: outStream.bytesWritten
})
})
})
}
MyCustomStorage.prototype._removeFile = function _removeFile(req, file, cb) {
fs.unlink(file.path, cb)
}
module.exports = function(opts) {
return new MyCustomStorage(opts)
}
for reactJs
here is a npm package
npm i react-image-file-resizer
for NodeJs
npm i react-image-file-resizer

How to upload a file to a certain folder in Cloudinary? Node JS

In cloudinary I have a folder called images, I want to upload the files into that folder.
I have done set up the cloudinary config. The storage options and the file filter has been done. In the request, I send the post request that will upload the file to cloudinary, but not to the folder. How can I upload a file to a certain folder in Cloudinary?
const storage = 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.match(/jpe|jpeg|png|gif$i/)) {
cb(new Error('File is not supported'), false);
return;
}
cb(null, true);
};
const upload = multer({
storage,
fileFilter
});
router.post('/', upload.single('profileImage'), async (req,res) => {
const result = await cloudinary.v2.uploader.upload(req.file.path);
})
When you're performing the upload request you can specify a set of options to be used for that upload. In these options, you can specify the 'public_id' (filename) and/or 'folder' of where the file should be stored.
For example, to upload a file to a folder called 'test', you can use the code below:
cloudinary.v2.uploader.upload(
"path/to/file",
{
folder: "test",
},
function(error, result) {
console.log(error,result);
}
);
You can find out all available options for the upload method via this section of the documentation:
https://cloudinary.com/documentation/image_upload_api_reference#upload_method

Custom Multer storage - Illegal operation when using Sharp

I'm using Multer together with Sharp to store images uploaded as part of an HTML form. I want to resize and transform the images before storing them on the disk and found this thread about how to do just that.
I thought I had set-up everything correctly, but when I try and upload an image I get:
Error: EISDIR: illegal operation on a directory, open 'C:\...\uploads'
Below is my code:
Routes.js:
var multer = require('multer');
var customStorage = require(path.join(__dirname, 'customStorage.js'));
var upload = multer({
storage: new customStorage({
destination: function (req, file, cb) {
cb(null, path.join(__dirname, 'uploads'));
},
filename: function (req, file, cb) {
cb(null, Date.now());
}
}),
limits: { fileSize: 5000000 }
});
...
app.use('/upload', upload.single('file'), (req, res) => { ... });
customStorage.js:
var fs = require('fs');
var sharp = require('sharp');
function getDestination (req, file, cb) {
cb(null, '/dev/null'); // >Implying I use loonix
};
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);
var outStream = fs.createWriteStream(path);
var transform = sharp().resize(200, 200).background('white').embed().jpeg();
file.stream.pipe(transform).pipe(outStream);
outStream.on('error', cb);
outStream.on('finish', function () {
cb(null, {
path: 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);
};
The error Error: EISDIR: illegal operation on a directory in this context indicates that you are setting Multer's destination to a directory when it should be the name of the destination file.
The destination is set in the line cb(null, path.join(__dirname, 'uploads')); in Routes.js. If you change this line to something like cb(null, path.join(__dirname, 'myDirectory\\mySubdirectory\\', myFilename + '.jpg')), it will work.

Rename uploaded images using MULTER

I am uploading the images using multer. They all are given random names (dec93b9f333c7a731723b06ce73c0bbc.jpg), which is very bad for SEO... Can you guys help me out, how to save the images with the pattern: 'fixed-name'+'random-name'.extension. Then at least part of the file would be readable for the google. Thanks!
app.set('images', '/var/www/images');
app.use(app.get('images'), express.static(app.get('images')));
var multerForImage = multer({
dest: app.get('images'),
onParseStart: function (file) {
console.log("Started parsing file stream", file);
},
onFileUploadStart: function (file) {
console.log('File recieved: ', file.originalname);
},
onFileUploadComplete: function (file, req, res) {
console.log("File upload complete");
var path = app.get('images') + "/" + file.name;
var user = req.session.user;
res.json({
success: true,
data: path
});
},
onFileUploadData: function (file, data, req, res) {
console.log('Data recieved for file upload');
},
onParseEnd: function (req, next) {
console.log("Parsing data end for file upload");
}
});
You can use the storage configuration.
app.set('images', '/var/www/images');
app.use(app.get('images'), express.static(app.get('images')));
var storage = multer.diskStorage({
destination: function (req, file, cb) {
cb(null, app.get('images'))
},
filename: function (req, file, cb) {
const randomPart = uuidV4(); // use whatever random you want.
const extension = file.mimetype.split('/')[1];
cb(null, 'fixed-name'+ randomPart + `.${extension}`)
}
})
var multerForImage = multer({
storage: storage,
...

Resources