NodeJS Multer: configuration not applied - javascript

I have a simple upload application written in NodeJS using Multer, which works fine. Here's the code:
var express = require('express'),
bodyParser = require('body-parser'),
qs = require('querystring'),
multer = require('multer'),
logger = require('morgan');
var config = require('./config'),
oauth = require('./oauth');
function extractExtension(filename) {
return filename.split('.').pop();
}
function getRandom(min, max) {
return Math.floor(Math.random() * max) + min;
}
var app = express();
//app.use(cors());
// Add headers
app.use(function(req, res, next) {
// Website you wish to allow to connect
res.setHeader('Access-Control-Allow-Origin', 'http://localhost:8080');
// Request methods you wish to allow
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, PATCH, DELETE');
// Request headers you wish to allow
res.setHeader('Access-Control-Allow-Headers', 'X-Requested-With,Authorization,content-type');
// Set to true if you need the website to include cookies in the requests sent
// to the API (e.g. in case you use sessions)
res.setHeader('Access-Control-Allow-Credentials', true);
// Pass to next layer of middleware
next();
});
// Multer
var momentUpload = multer({
dest: './uploads/',
limits: {
fileSize: 256 * 1024 * 1024
},
rename: function(fieldname, filename) {
return Date.now() + '-' + getRandom(100000, 999999) + '.' + extractExtension(filename);
},
onFileUploadStart: function(file) {
console.log(file.originalname + ' is starting ...')
},
onFileUploadComplete: function(file) {
console.log(file.fieldname + ' uploaded to ' + file.path)
}
}).single('file');
app.set('port', 4000);
app.use(logger('dev'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({
extended: true
}));
app.post('/file/upload', [oauth.ensureAuthenticated, momentUpload, function(req, res) {
console.log(req.body); // form fields
console.log(req.file); // form files
res.status(204).end();
}]);
// Start the Server
app.listen(app.get('port'), function() {
console.log('Metadata store env: ' + config.METADATASTORE_ENV);
console.log('Express server listening on port ' + app.get('port'));
firebase.authenticate();
console.log('Connected to Firebase');
});
The problem is, however, that the configuration of Multer doesn't seem to be working at all. The destPath works, and files appear in the folder I provided (./uploads/). Larger file sizes are allowed (e.g. a file of 400MB, while the options clearly state 256MB), and the callback functions aren't fired once. There is no error message. Any idea what I'm doing wrong here? I followed guides on Google and on the official page, but can't get it to work.

First of all, multer has changed its API recently, so it no longer accepts rename, onFileUploadStart or onFileUploadComplete!
We can take a look at the API here https://github.com/expressjs/multer, so lets analyse the new way things work!
NOTE: If you haven't updated your multer version, I strongly advise you to, because older versions are suspected to have a security breach.
Basic Usage
Multer accepts an options object, the most basic of which is the dest
property, which tells Multer where to upload the files. In case you
omit the options object, the files will be kept in memory and never
written to disk.
By default, Multer will rename the files so as to avoid naming
conflicts. The renaming function can be customized according to your
needs.
The options object also accepts fileFilter(a function to control which files are uploaded) and limits(an object that specifies the size limits) parameters.
So, your code would look like this (concerning only the multer part and considering you'd want to use all options which you don't have to):
// Multer
var momentUpload = multer({
dest: './uploads/',
limits: {
fileSize: 256 * 1024 * 1024
},
fileFilter: function(req, file, cb) {
// The function should call `cb` with a boolean
// to indicate if the file should be accepted
// To reject this file pass `false`, like so:
cb(null, false)
// To accept the file pass `true`, like so:
cb(null, true)
// You can always pass an error if something goes wrong:
cb(new Error('I don\'t have a clue!'))
}
}).single('file');
If you want to have more control of the storing of files you can use a storage engine. You can create your own or simply use the ones available.
The available ones are: diskStorage to store files on disk or memoryStorage to store files in the memory as Buffer objects.
Since you clearly want to store files in the disk, let's talk about the diskStorage.
There are two options available: destination and filename.
destination is used to determine within which folder the uploaded
files should be stored. This can also be given as a string (e.g.
'/tmp/uploads'). If no destination is given, the operating system's
default directory for temporary files is used.
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.
filename is used to determine what the file should be named inside the folder. If no filename is given, each file will be given a random name that doesn't include any file extension.
So, your code (concerning only the multer part) would look like this:
// Multer
//Storage configuration
var storageOpts = multer.diskStorage({
destination: function (req, file, cb) {
//Choose a destination
var dest = './uploads/';
//If you want to ensure that the directory exists and
//if it doesn't, it is created you can use the fs module
//If you use the following line don't forget to require the fs module!!!
fs.ensureDirSync(dest);
cb(null, dest);
},
filename: function (req, file, cb) {
//here you can use what you want to specify the file name
//(fieldname, originalname, encoding, mimetype, size, destination, filename, etc)
cb(null, file.originalname);
}
});
var momentUpload = multer({
storage: storageOpts,
limits: {
fileSize: 256 * 1024 * 1024
},
fileFilter: function(req, file, cb) {
// The function should call `cb` with a boolean
// to indicate if the file should be accepted
// To reject this file pass `false`, like so:
cb(null, false)
// To accept the file pass `true`, like so:
cb(null, true)
// You can always pass an error if something goes wrong:
cb(new Error('I don\'t have a clue!'))
}
}).single('file');
Hope it helped! :)

Related

Uploading a file using multer in Node JS

Can you please explain in detail what is happening in these lines of code?
Why we are storing a function in the object with a callback?
var storage = multer.diskStorage({
destination: function (req, file, cb) {
cb(null, '/tmp/my-uploads')
},
filename: function (req, file, cb) {
cb(null, file.fieldname + '-' + Date.now())
}
})
var upload = multer({ storage: storage })
The multer library is going to call the destination function when it wants to know what directory you want to put the uploads into. It will call the filename function when it wants to now what filename you want to give a particular upload.
The reason both of these are functions is so that you have the option of examining the incoming request to compute a directory or filename that is appropriate for that specific request. For example, if you wanted to store all files uploaded by one particular user in a directory for that specific user, you might look at the req.session object to determine which user it is making the upload and then you would compute the appropriate directory for that user.
The destination option may be provided as a plain string instead of a function if the value is known and is constant for all requests. For example, you could do this:
var storage = multer.diskStorage({
destination: '/tmp/my-uploads',
filename: function (req, file, cb) {
cb(null, file.fieldname + '-' + Date.now())
}
})
The reason that you can communicate the final result via a callback rather than just a plain return value is in case you need to do some asynchronous operation (like look something up in a database) in order to compute what the directory or filename should be. Communicating the result back by calling their supplied cb(...) function allows you do asynchronous operations and communicate back the result asynchronously.

Dynamically create folder and upload file in multipart request Node js

I'm trying to dynamically create folders when user loads a file (this is because I need to store every user files separately so there's no conflict if a file is deleted) and create the file path in my computer so I can store it in my many to many relationship table in the database (users can have many files and many files can belong to different users, that's why I can't store them together) but I cannot get this with my current logic. I could create different name with timestamps but it would get messy.
I'm using multer and Express. The form data that's been sent is the one on the attached image.
const express = require('express');
const app = express();
const multer = require('multer');
const bodyParser = require('body-parser');
const fs = require('fs');
router.use(bodyParser.json());
router.use(bodyParser.urlencoded({ extended: false }));
let storage = multer.diskStorage({
destination: function (req, file, cb) {
//req.body is empty
let path = `./public/uploads/${req.body.id}`;
if (!fs.existsSync(path)) {
fs.mkdirSync(filesDir);
}
cb(null, path);
},
filename: function (req, file, cb) {
cb(null, `${file.originalname}`)
}
})
let upload = multer({storage:storage});
var cpUpload = upload.fields([{ name: 'file', maxCount: 1}, { name: 'id'}]);
router.post('/uploadSingFile', cpUpload,(req,res)=>{
console.log(req.body); req.body is ok
res.send({status:200});
});
Somehow the request object doesn't have the body in the destination function but does in the post request. I thought this would work given the way middlewares work in Node, the storage function should have the same request object that the post request has.
You can use newly generated uuid for filenames to have it unique inside the folder. For the folder name, instead of getting it from request body(req.body.id), use something like userId which can make sure the folder belongs to particular user.

return filename of uploaded file with multer and nodejs

I am creating a simple application as a learning experience with ionic 2 that connects to a node server running multer to upload images. The problem I am running into is, I need to get the final file name from multer back to the app that uploaded it, to save it locally. I am using the Auth and User modules from #ionic/cloud-angular for authentication and user information. The User object has a details object with things like "name", "email", "username", "image", ...
So, when an image is uploaded to the node server and multer saves it, I need to get that filename back to the application so I can update the user.details.image property.
Here is a simple version of my node server...
var express = require('express');
var http = require('http');
var bodyParser = require('body-parser');
var multer = require('multer');
var cors = require('cors');
var postStorage = multer.diskStorage({
destination: function(req, file, callback) {
callback(null, './uploads');
},
filename: function(req, file, callback) {
let fileName = '', postName;
if(typeof req.body.postName !== "undefined") {
postName = req.body.postName.toLowerCase().replace(/ /g, '-');
filename += postName;
}
fileName += new Date().getTime();
fileName += ".png";
callback(null, fileName);
}
});
var app = express();
app.set('port', process.env.PORT || 3000);
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({extended: false}));
app.use(cors());
app.post('/upload', function(req, res) {
var uploadPost = multer({storage: postStorage}).single('post_image');
uploadPost(req, res, function(err) {
if(err) {
return res.end("error uploading file");
}
res.end("file uploaded");
});
});
app.listen(app.get('port');
console.log("app listening on port: " _ app.get('port'));
Is there a way to have it send back the final file name that multer saved? Something like res.json({fileName: fileName}); res.end();
I really don't know at this point, and all the tutorials I can find on multer just show how to create a new filename and save a file in disk or database or memory, but nowhere does it show how you can actually get that filename back to the application that uploaded it.
Thank you in advance for any help, I really appreciate it. If you need to see the ionic 2 code that uploads the stuff please let me know and I will update the post with that code as well. Thank you.
If you are uploading single file you can get filename using req.file.filename
if you upload multiple files req.files
Once you got file name , you can send it to client using res.send({filename:FileName});

Creating dynamic link by requesting data from front-end in Node.js and Express.js

This is kind of hard for me to explain but I am trying to shorten my code by creating one link instead of 50 for downloading a form. I will try to explain this a bit better using my code.
I have 5 get requests that do exactly the same thing but download a different file.
router.get('/form1', function (req, res) {
var file = __dirname + '/../public/forms/form1.pdf';
res.download(file);
});
router.get('/form2', function (req, res) {
var file = __dirname + '/../public/forms/form2.pdf';
res.download(file);
});
etc...
and my front-end link are;
FORM 1
FORM 2
etc...
Is there anything I can do to make this a more dynamic? The only way I can think of is something like this;
router.get('/:formName', function (req, res) {
// some how do a "req.params.formName"
var file = __dirname + '/../public/forms/' + req.params.formName + '.pdf';
res.download(file); // Set disposition and send it.
});
But I don't know how I will get the formName or if thats even possible.
Here are some more options to clarify:
Option 1: If you have a folder on the server with a fairly manageable directory structure, simply use express.static to map the physical folder to a virtual one with automatic download:
app.use('/download', express.static(path.join(__dirname, '/public/forms/')))
This will result in any link from the front-end with href='/download/something.pdf' working as long as that file is on the server in the path you mapped (i.e. in /public/forms).
Option 2 (which David E above answered in essence): In your original code, if you wanted to generate a path handler for a link that looks like /download/form1, /download/form2, it's a very minor modification:
router.get('/download/:formNumber', function (req, res) {
var file = __dirname + '/public/forms/' + req.params.formNumber + '.pdf';
res.download(file);
});
Option 3: You want to authenticate access to the files and potentially support multiple, complex URL schemes to a single handler that can lookup the appropriate physical path and send the file:
router.get('/download/:path[forms|images|files]/:formNumber1', fileRequestHandler);
router.get('/public/downloadFolder2/:formNumber2', fileRequestHandler);
router.get('/public/downloadFolder3/:formNumber3', fileRequestHandler);
function fileRequestHandler(req, res) {
// Check authentication here - example below from Passport
if(!req.isAuthenticated())
return res.status(401).send({err: 'Unauthorized'});
// Check which form number is supplied and map to appropriate physical file
if(req.params.formNumber1) {
// in this example, req.params.path can be one of three allowed sample values - forms or images or files
var file = __dirname + '/public/' + req.params.path + '/' + req.params.formNumber + '.pdf';
res.download(file);
} else if(req.params.formNumber2) {
// etc.
}
}
Note: Ezra Chang's point about path validity is important.
This response assumes that your route lives in index.js.
router.get('/form/:formName', (req, res, next) => {
res.download(`${__dirname}/../public/forms/${req.params.formName}.pdf`);
});
FORM 2
Be careful about your path. I don't know whether you can start at a directory, go up a level, then down again.

express.js unique directory for each user

I have an express.js app, and I am using drywall in order to manage the user system.
When a user signs up, I want a directory to be generated for that user, and I want that user to be able to upload files to that directory and view those files through his or her account.
I am not entirely sure, but I think that most likely for the directory generation I will have to do that inside views/signup/index.js, and that the user can only upload files to his or her directory if logged in.
However, I'm a bit stuck when it comes to saving and displaying the files. I have little experience with server side code, so implementing actions such as accessing files is slightly beyond my scope.
Thanks in advance to those who help.
So first you should create a folder for each user by using fs.mkdir:
http://nodejs.org/api/fs.html#fs_fs_mkdir_path_mode_callback
Let's say you want to create these folders into your app root / images:
Example:
var fs = require('fs');
fs.mkdir(__dirname + '/images/' + userId, function(err) {
if (err) {
/* log err etc */
} else {
/* folder creation succeeded */
}
});
You should probably use the userId for the folder name (since it's easier than trying to strip out the bad characters from the username itself, and this will also work in the future if the user changes his username).
The second thing you need to do is to allow the user to upload files (but only if he is logged in and into the right folder). It's better to not include the bodyParser middleware for all routes, but instead include the json && urlencoded middleware for all routes (http://www.senchalabs.org/connect/json.html && http://www.senchalabs.org/connect/urlencoded.html) and the multipart middleware only for the upload url ( http://www.senchalabs.org/connect/multipart.html && example: https://github.com/visionmedia/express/blob/master/examples/multipart/index.js ).
An example:
app.post('/images', express.multipart({ uploadDir: '/tmp/uploads' }), function(req, res, next) {
// at this point the file has been saved to the tmp path and we need to move
// it to the user's folder
fs.rename(req.files.image.path, __dirname + '/images/' + req.userId + '/' + req.files.image.name, function(err) {
if (err) return next(err);
res.send('Upload successful');
});
});
Note: in the example above I've taken into consideration that req.userId is populated with the id of the user by an auth middleware.
Showing the images to the user if he has the rights to see them (the auth middleware should be applied for this path as well):
app.get('/images/:user/:file', function(req, res, next) {
var filePath = __dirname + '/images/' + req.userId + '/' + req.params.file;
fs.exists(filePath, function(exists) {
if (!exists) { return res.status(404).send('Not Found'); }
// didn't attach 'error' handler here, but you should do that with streams always
fs.createReadStream(filePath).pipe(res);
});
});
Note: in production you might want to use send instead, that example was just demoing the flow ( https://github.com/visionmedia/send ).

Categories

Resources