How to call a multer upload function outside of handler - javascript

I would like to dynamically set the storage destination for uploading a file using multer in my express app, as well as the name of the file.
I have an express route that handles a post request as shown:
EJS:
<form action="/upload" method="post" enctype="multipart/form-data">
<input type="file" name="somename">
<select name="p">
<option selected>ABC</option>
<option>DEF</option>
</select>
<input type="number" name="dtYear" value="2020">
<button type="submit">Upload</button>
</form>
ExpressJS:
let storageDest = './public/content/dts/' ;
let fileName;
const storage = multer.diskStorage({
destination: (req, file, cb) => {
cb(null, storageDest)
console.log(file);
},
filename: (req, file, cb) => {
if (fs.existsSync(storageDest + file.originalname)){
fileName = file.fieldname + Date.now() + path.extname(file.originalname)
} else{
fileName = file.originalname;
}
cb(null, fileName)
}
})
const upload = multer({storage: storage})
app.post('/upload', upload.single('somename'), (req, res, next) => {
storageDestiantion += req.body.p.toLowerCase() + '/';
fileName = req.body.dtYear;
res.send('Thank you for uploading!')
})
However, I can only change the storageDestination and fileName variables before the multer upload.single() function is called for them to have any effect, as if I change them after the function call, the file is already uploaded.
Is there a way to call the upload.single() function after making the above changes, or does it have to be a parameter of the app.post() function - if it does, is there a workaround to achieve what I desire?

Related

Image uploaded in my folder without extension with node js and multer

I have a weird problem with node and multer.
My script to upload an image is working, but the images are being uploaded without the extension (jpeg, png, etc).
Just like that:
4a8a1400d04b5ccdfdb829aa5ee62763
If I add an extension (jpeg) to the image, then it's displayed.
My code is like that:
const multer = require('multer');
var upload = multer({ dest: __dirname +'/uploads/' });
app.post('/test.html', upload.single('avatar'),function (req,res,next){
console.log("minetype"+req.file.mimetype);
res.render('test', {})
});
And my form:
<form action="/test.html" method="post" enctype="multipart/form-data" name="monForm">
<input type="file" name="avatar" />
<input type="submit" value="Upload">
</form>
you can configure filename with this for exemple :
which will preserve the original extension and still generate a random name using uniqid package. (feel free to replace it with something else if needed)
const uniqid = require('uniqid');
const path = require('path');
const multer = require('multer');
let upload = multer({
storage: multer.diskStorage({
destination: function (req, file, cb) {
cb(null, `${__dirname}/uploads/`);
},
filename: function (req, file, cb) {
cb(null, uniqid() + path.extname(file.originalname));
},
}),
});

Custom file name from frontend in Multer

I'm uploading a file using FormData and receiving it server-side using Multer. Everything works as expected, except since I'm using FileSystem API on the front-end (https://developer.mozilla.org/en-US/docs/Web/API/DataTransferItem/webkitGetAsEntry), the files I'm uploading come from sub-directories. Multer seems to only see the filename, even if I explicitly set an alias for the file as I append it to form data (https://developer.mozilla.org/en-US/docs/Web/API/FormData/append). It also seems like Multer performs its logic prior to the rest of my request handler and does not see the parameters I set on the body. How do I get multer to see the full path?
Here is a simplified version of what I currently have setup:
Client (alias represents full name with path, file.name is the base name automatically set by FileSystem API):
function upload(file, alias) {
let url = window.location.origin + '/upload';
let xhr = new XMLHttpRequest();
let formData = new FormData();
xhr.open('POST', url, true);
return new Promise(function (resolve, reject) {
xhr.addEventListener('readystatechange', function(e) {
if (xhr.readyState == 4 && xhr.status == 200) {
resolve(file.name);
}
else if (xhr.readyState == 4 && xhr.status != 200) {
reject(file.name);
}
})
formData.append('file', file, alias || file.name); // this should in theory replace filename, but doesn't
formData.append('alias', alias || file.name); // an extra field that I can't see in multer function at all
xhr.send(formData);
});
}
Server:
const storage = multer.diskStorage({
destination: function (req, file, cb) {
cb(null, 'uploads/');
},
filename: function (req, file, cb) {
// neither req nor file seems to contain any hint of the alias here
cb(null, file.originalname);
}
});
const upload = multer({storage: storage});
const bodyParser = require('body-parser');
app.use(bodyParser.json());
app.post('/upload', upload.single('file'), function (req, res, next) {
// by this time the file seems to already be on disk with whatever name multer picked
if (req.file) {
res.status(200).end();
} else {
res.status(500).end();
}
});
In order to get this to work, use the preservePath option when configuring multer. The following will work:
const upload = multer({storage: storage, preservePath: true});
However, it's important to note, multer will not create the directories or subdirectories. Those have to be created beforehand. (I tested this too. If directories are created and empty, upload succeeds, however, if directories do not exist, uploads fail).
In their readme, they say:
"Note: You are responsible for creating the directory when providing destination as a function. When passing a string, multer will make sure that the directory is created for you."
A follow-up to that note would be: "you are responsible for creating any sub-directories too".
The relative paths of files uploaded will be accessible in originalname property. So, backend would look like this: (as you had it, but with updated comments)
const storage = multer.diskStorage({
destination: function (req, file, cb) {
cb(null, 'uploads/');
},
filename: function (req, file, cb) {
// If you uploaded for example, the directory: myDir/myFile.txt,
// file.originalname *would* be set to that (myDir/myFile.txt)
// and myFile.txt would get saved to uploads/myDir
// *provided that* uploads/myDir already exists.
// (if it doesn't upload will fail)
// /* if( [ uploads/myDir doesn't exist ] ) { mkdir } */
cb(null, file.originalname);
}
});
Helpful tip:
On the front end, I found it easier to test directory / subdirectory upload with: (tested on Chrome latest ok)
<form action="/uploads/multipleFiles" method="post" enctype="multipart/form-data">
<input type="file" name="multiple" webkitdirectory accept="text/*" onchange="console.log(this.files)" />
<input type="text" name="tester" value="uploadTester" />
<input type="submit"/>
</form>
If you want to upload Passport image as a front and back side then pass parameter from frontend like this user:"username" and type:"front" OR type:"back"
then Use it in node side like this
const upload = multer({
const storage = multer.diskStorage({
destination: function (req, file, cb) {
cb(null, 'uploads/passport/');
},
filename: function (req, file, cb) {
cb(null, req.body.user+"-"+req.body.type+".jpg");
}
})
});

NodeJS - multer - change filename depending on request attributes

I know that I can change the filename with multer by means of the storage object like following:
const storage = multer.diskStorage({
destination: (req, file, cb) => {
cb(null, process.env.UPLOAD_DIR);
},
filename: (req, file, cb) => {
cb(null, 'bla.png');
}
});
const upload = multer({ storage: storage } );
My request, besides having the file, also contains some text attributes such as name: myPic.png.
Is it possible to dynamically change the filename dependent on other request attributes or within the controller like following:
filename: (req, file, cb) => {
cb(null, `${req.body.name}.png`);
}
or
router.post('/upload', upload.single('pic'), myController.upload);
/* in controller */
upload = async (req: Request, res: Response) => {
try {
/* change the filename of multer here? */
} catch (err) {
winston.error(`Error while uploading: ${err.message}`);
winston.error(`Stack trace: ${err.stack}`);
sendJSONResponse(res, err, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
Multer is the middleware which both populates req.body AND stores the file.
Also, when it reaches the filename() function, there is no guarantee that the text fields will be populated in req.body because it depends on which order the client sends them in (see last note).
From what I see, you have two options:
1) Rename the uploaded file after the multer upload middleware does its thing and populates req.body as well as req.file. So in your controller upload middleware, you'd do something like:
if (req.file) {
fs.renameSync(req.file.path, req.file.destination + req.body.name);
}
2) Change the request body text field into a query parameter. Then, inside filename() you can do a req.query.name.
Con: Not a very RESTful design, but maybe that is not so important to you.
According to the multer documentation it does not have access to req.body for other additional fields, if you test it it receives an undefined value, then a not so perfect but functional solution is the following, once the file is uploaded you can rename it as follows.
Add the native class fs for access to files option
const fs = require('fs');
In diskStorage configuration add the name you want, for example bla.png
var storage = multer.diskStorage({
destination: path.join('public/images/'),
filename: function ( req, file, cb ) {
cb(null, 'bla.png');
}
});
The form with the text field for the custom name
<form action="/upload" enctype="multipart/form-data" method="POST">
<input type="file" accept="image/*" name="photo" >
<br><!--here is the custom file name-->
<input type="text" name="file_name">
<br>
<button type="submit">Send</button>
</form>
Within the post path, once you have sent the file whose name will be bla.png, you can replace that name with the one in a field of the form by accessing req.body.field_name
router.post('/upload', upload.single('photo'), (req, res) => {
//Here change the file name bla.png for the new value in req.body.field_name + original ext of file
fs.renameSync(req.file.path, req.file.path.replace('bla.png',
req.body.field_name + path.extname(req.file.originalname)));
if(req.file) {
res.json(req.file);
}
else throw 'error';
});

Upload image to MongoDB with Nodejs and express

I'm trying to upload a image using multer in nodejs. I configured multer to save uploaded images in the "upload" directory and when the form is submitted, the image is sent to the directory as it should. But I'm trying to save the path to that image (req.file.path) to a mongodb database and this is not working. I'm new to nodejs and mongodb and I can't figure out what the problem is.
This is the error I get:
ObjectParameterError: Parameter "obj" to Document() must be an object, got assets\uploads\uploaded_file-1518264794720.jpg
at new ObjectParameterError (C:\xampp\htdocs\projets\NodeApp01\node_modules\mongoose\lib\error\objectParameter.js:23:11)
at model.Document (C:\xampp\htdocs\projets\NodeApp01\node_modules\mongoose\lib\document.js:55:11)
at model.Model (C:\xampp\htdocs\projets\NodeApp01\node_modules\mongoose\lib\model.js:59:12)
at new model (C:\xampp\htdocs\projets\NodeApp01\node_modules\mongoose\lib\model.js:3750:13)
Here is my index.js:
var express=require('express');
var app=express();
var fs=require('fs');
var multer=require('multer');
app.set('view engine','ejs');
app.use(express.static('assets'));
var mongo=require('mongoose');
mongo.connect('mongodb://username:pass#database...');
var schema=new mongo.Schema({
item:String
});
var model1= mongo.model('todomod',schema);
var storage = multer.diskStorage({
destination: 'assets/uploads/',
filename: function (req, file, cb) {
cb(null, file.fieldname + '-' + Date.now()+ '.jpg')
}
})
var upload = multer({ storage: storage });
app.get('/',function(request,response){
response.render('index.ejs');
});
app.get('/add_item',function(request,response){
response.render('add_item.ejs');
});
app.post('/add_item',upload.single('uploaded_file'),function(req,res){
console.log(req.body);
console.log(req.file);
model1(req.file.path).save(function(err,data){
if(err) throw err
res.json(data);
})
});
app.listen(80);
console.log('Application listening on port 80....');
And here is my view (.ejs) for the form:
<body>
<% include partials/navigation.ejs %>
<h1>Adding an item to the shop</h1>
<form id="theform" action="add_item" method="POST" enctype="multipart/form-data">
Name of item:<input type="text" name="item" class="thedata"><br>
<br>
Photo of item:<input type="file" name="uploaded_file" class="thefile">
<button type="submit">Add item</button>
</form>
</body>
The issue is you are passing uploaded file path instead of file object, You can do like this to save uploaded file path in mongodb:
let db_data = {
item : req.file.path
};
model1(db_data ).save(function(err,data){
if(err) throw err
res.json(data);
})

req.file is undefined (multer, node.js)

I've been trying to upload an image for a while now, but req.file is still undefined. Can someone see why?
this is my page. I am able to pick an image when I click the '+' glyphicon, but on the server side req.file is still empty.
EJS file
input[type="file"] and input[type="submit"] have css styles display: none
<form action="/profile/addProfilepicture" method="post" id="form" enctype="multipart/form-data">
<span id="upload" class="glyphicon glyphicon-plus-sign"></span>
<label for="profilePic"></label>
<input id=profilePic type='file' />
<input type="submit">
</form>
<img class="profileImg"
src="<%="images/pexels-photo-370799.jpeg"%>"
alt="fail">
Client JS file
When I click the '+'glyphicon it lets me pick an image. When I do this, this will trigger the form to submit and send a post request.
$("#upload").on('click',function() {
$("input[type='file']").click();
});
$('input[type="file"]').change(function (e) {
$("input[type='submit']").click()
});
server side JS
On the server side it stops at:
TypeError: Cannot read property 'filename' of undefined
at C:\Users\Tijl Declerck\Desktop\projects\digitalNomadApp\routes\profile.js:27:38
at Immediate._onImmediate (C:\Users\Tijl Declerck\Desktop\projects\digitalNomadApp\node_modules\multer\lib\make-middleware.js:53:37)
at runCallback (timers.js:793:20)
at tryOnImmediate (timers.js:751:5)
at processImmediate [as _immediateCallback] (timers.js:722:5)
The console.logs I tried gave me this: req.body returns an empty object and req.file returns undefined.
var express = require('express');
var router = express.Router();
var multer = require('multer');
var User = require('../models/Users');
var storage = multer.diskStorage({
destination: function (req, file, cb) {
cb(null, './public/uploads/profilePics')
},
filename: function (req, file, cb) {
cb(null, file.fieldname + '-' + Date.now())
}
});
var upload = multer({ storage: storage }).single('myImage');
router.post('/addProfilePicture', function (req, res) {
var profilePicUrl = '';
upload(req, res, function (err) {
if (err) {
// An error occurred when uploading
} else {
console.log(req.file);
profilePicUrl = req.file.filename;
User.update({username: req.user.username}, {'profilePic.uploaded': true, 'profilePic.link': profilePicUrl}, function(err, doc){
console.log('THIS IS DONE')
});
}
});
});
You have to provide a name to your file input and it should match the single method's name, this is from multer doc:
.single(fieldname)
Accept a single file with the name fieldname. The single file will be
stored in req.file.
This is not well documented but fieldname refers to input name attribute
EJS file
<input id='profilePic' name='myImage' type='file' />
Express
...
var upload = multer({ storage: storage }).single('myImage');
...

Categories