How to Use a Dynamic Filepath using Formidable in Node JS? - javascript

My uploaded form has a file and a field "filepath" which is dynamically generated and contains the desired filepath of the file I'm uploading.
Example:
filepath: "assets/images/asset-01-02/"
I'm trying to set this field as a variable so I can save the file to this directory to keep uploaded files organized.
Current code:
const cors = require('cors');
const express = require('express');
const app = express();
const formidable = require('formidable');
app.use(cors());
app.options('*', cors());
app.post('/upload', (req, res) => {
var form = new formidable.IncomingForm();
form.parse(req, (err, fields, files) => {
if (err) {
console.log('Error', err)
throw err
}
console.log(fields.filepath); //Output e.g. "assets/images/asset-01-02/"
})
form.on('fileBegin', (name, file) => {
//Need to use fields.filepath after '/public/uploads/'
//e.g. __dirname + '/public/uploads/' + fields.filepath + file.name;
file.path = __dirname + '/public/uploads/' + file.name;
});
form.on('file', (name, file) => {
console.log('Uploaded ' + file.name);
});
});
app.listen(80, function(){
console.log('Listening on Port 80...');
});
I need to get the fields.filepath value passed to the form.on('fileBegin') function but I'm not sure how to. I haven't come across any examples of this specific issue.

As far as I am understanding your question, you are trying to send 'filepath' with 'multipart/form-data' or from client to server with uploaded files.
and you are trying to catch and set this 'filepath' using form.parse 'fields' into form.parse callback.
your code do not work simply because form.on('fileBegin') callback will execute before form.parse callback.
here is execution order for callback in Formidable.(can change due to asynchronous functions)
form.on('fileBegin')
form.on('file')
form.parse()
form.on('end')
instead passing 'filepath' with 'form-data', send it with query like localhost/upload?filepath=assets/images/asset-01-02
and get it like
var filepath = req.query.filepath;
in your code

Related

Uploading a file using Node without changing to a new page

I've been following some tutorials on how to upload a file using node and I've had success in actually uploading the file (I've primarily used https://www.geeksforgeeks.org/file-uploading-in-node-js/). However, every time it uploads the file, it changes to a new page. Is there a way to stay on the same page after uploading the photo, or do I have to create an HTML file with the same name with the same HTML as before the upload? The working code is below:
const express = require("express")
const path = require("path")
const multer = require("multer")
const app = express()
app.use(express.static(__dirname + '/public'));
var storage = multer.diskStorage({
destination: function (req, file, cb) {
cb(null, "uploads")
},
filename: function (req, file, cb) {
cb(null, file.fieldname + "-" + Date.now()+".jpg")
}
})
var upload = multer({
storage: storage,
fileFilter: function (req, file, cb){
var filetypes = /jpeg|jpg|png/;
var mimetype = filetypes.test(file.mimetype);
var extname = filetypes.test(path.extname(
file.originalname).toLowerCase());
if (mimetype && extname) {
return cb(null, true);
}
cb("Error: File upload only supports the "
+ "following filetypes - " + filetypes);
}
}).single("myfile");
app.get("/",function(req,res){
res.render("Signup");
})
app.post("/uploadCode",function (req, res, next) {
upload(req,res,function(err) {
if(err) {
res.send(err)
}
else {
res.send("Success, Image uploaded!")
}
})
})
app.listen(8000,function(error) {
if(error) throw error
console.log("Server running on port 8000")
})
If you do the upload from a form where the browser submits the form for you automatically via a submit button, then it will be a browser form POST and the browser will automatically display whatever that POST request returns as the response.
If you, instead, upload form a form with a Javascript Ajax call to do the form POST, then the form response just comes back to your Javascript and nothing happens to the current web page in the browser. Your Javascript can receive that response and then decide what to do with it (if anything).
You've shown your server-side code here, but it's actually the client-side that controls what happens after the response from the upload is received back from the server. So, the changes would have to be made on the client-side to submit your form via Javascript.

Unable to send file with PDF( *.pdf) extension

I am trying to read a file using node's readFile method and then send it as response so that user can download it.
This is my code:
async function(req, res, next) {
const query = { id: req.params.id };
// #ts-ignore
const fileURL = await Patient.findOne(query).select('retinaReportURL -_id');
// #ts-ignore
const splittedPath = fileURL.retinaReportURL.split('\\');
const fileName = splittedPath[splittedPath.length-1].split('.')[0] + '.pdf';
const filePath = path.join(__dirname, '..', '..', 'Invoices', fileName);
fs.readFile(filePath, (err, _data) => {
if (err) {
return next(new APIError('Unable to fetch file at the moment please try again later', 500))
}
res.send(data);
});
}
Now my file path is proper with a valid PDF inside the Invoices folder.
But the, when the file is getting downloaded, am facing two issues:
The .pdf extension is not there.
The file name by which it's get downloaded is the id I am passing as request param.
I tried setting a response header to text/pdf but no luck.
What am I doing wrong here??
Express has a helper for this to make it easy for you,
I assume you have following path,
const fileName = splittedPath[splittedPath.length-1].split('.')[0] + '.pdf';
const filePath = path.join(__dirname, '..', '..', 'Invoices', fileName);
app.get('/download', function(req, res)
{
res.download(filePath); // Set file name with its path
});

Express – download file from an external URL

I have a file stored on an external server. I want to be able to call GET request to my own NodeJS server (using express). What I'm currently doing is almost OK, but it does not trigger browser to download the file (no browser UI for the download is shown):
const express = require('express');
const app = express();
app.get('/download-file', (req, res) => {
const externalRequest = http.request({
hostname: 'my.external-server.com',
path: '/my/path/my-file.zip',
}, (externalRes) => {
res.setHeader('Content-Disposition', 'attachment; filename="MyFile.zip"');
externalRes.pipe(res);
});
return externalRequest.end();
});
app.listen(8080, () => console.log('Server is listening'));
What am I missing here? I see that triggering a GET request to localhost:8080/download-file is actually fetching it, but no UI for download is shown.
This is the code that is running in one of my pet projects, hope it helps.
It pipes the download request ok, but there is no size info for the download, so it becames one of that downloads that you dont know when will finish.
const http = require('http')
app.get('/down_file/:file_name', (req, res) => {
const fileName = req.params.file_name
const url = "http://externalUrl/" + fileName
var externalReq = http.request(url, function(externalRes) {
res.setHeader("content-disposition", "attachment; filename=" + fileName);
externalRes.pipe(res);
});
externalReq.end();
})

Full control over files sent with ajax in nodejs

I use multer to parse multiple files sent as multipart/data-form with axios
...
const storage = multer.diskStorage({
destination: './gallery',
filename(req, file, cb) {
(1) ....
},
});
const upload = multer({ storage });
router.post('/products', upload.array('images'), (req, res, next) => {
Product.create(...)
.then((product) => {
(2) ...
})
.catch(..)
})
...
at this point everything is fine and my images are saved.
the problem is that i want to make a loop in (1) or (2) and name my files like this
files.forEach((file, index) => {
// rename file to => product_id + '_' + index + '.jpeg'
}
For example if i have 3 files they will be named to
5a9e881c3ebb4e1bd8911126_1.jpeg
5a9e881c3ebb4e1bd8911126_2.jpeg
5a9e881c3ebb4e1bd8911126_3.jpeg
where 5a9e881c3ebb4e1bd8911126 is the id of the product document saved by mongoose.
how to solve this naming issue ?
is multer the best solution cause i want full control over my files ?
Is there a better approach with another node package ?
is it good to send images as multipart/data-form or data URL base64 ?
This is easy, as long as you understand how express works. So before jumping to solution its important to have a clear understanding.
When you have a express code like below
router.post('/abc', function(req, res) {res.send('hello world');})
Express passes the request from chains of middlewares/functions. Now each function gets req, res, next parameters. The next is function, which a middleware is suppose to call when the processing is complete. If the middleware decides not to call next the request ends there and no more middlewares are called further.
When we used function(req, res) {res.send('hello world');}, we didn't take the next parameter at all, which means we are not interested in any other code to do anything. Now getting back to our problem
router.post('/products', upload.array('images'), (req, res, next) => {...}
You have used upload.array('images') first and then your actual product creation code. So I would show two approaches to solve this problem
One more middleware to rename the files
router.post('/products', upload.array('images'), (req, res, next) => {
Product.create(...)
.then((product) => {
req.product = product
next();
})
.catch(..)
}, (req, res, next) => {
//Get the product id using req.product
//Move the files as per the name you desire
})
Reverse the processing order
In this approach you first create the product and then let image processing happen. I have created a sample for the showing the same
let express = require('express');
let bodyParser = require('body-parser');
app = express();
let multer = require('multer');
const storage = multer.diskStorage({
destination: './gallery',
filename: (req, file, cb) => {
console.log('Product id - ' + req.product_id);
cb(null, req.product_id + '.js');
},
});
const upload = multer({ storage });
app.all('/', (req, res, next) => {
console.log('Hello you');
promise = new Promise((resolve) => {
// simulate a async product creation
setTimeout(() => resolve(1234), 100);
});
promise.then((product_id) => {
console.log('create the product and get the new product id')
// set the product id in the request object, so the multer
// filename function can access it
req.product_id = product_id;
res.send('uploaded files');
if (next)
next();
});
}, upload.array('images'));
module.exports = {
app
};
app.listen(8020);
And testing it using postman works fine
Edit: 19-Mar-2018
For multiple files you can easily update your filename function code like below
const storage = multer.diskStorage({
destination: './gallery',
filename: (req, file, cb) => {
req.file_id = req.file_id || 0;
req.file_id++;
console.log('Product id - ' + req.product_id);
cb(null, req.product_id +'_' + req.file_id +'.js');
},
});
This will make sure that you get all the files for that product. Now coming to your questions
how to solve this naming issue ?
This answer already does that
is multer the best solution cause i want full control over my files ?
I can't say, as long it works and does what you want, it should be good enough
Is there a better approach with another node package ?
I couldn't find lot of packages. But you can explore this if you want
is it good to send images as multipart/data-form or data URL base64 ?
I would use multipart/data-form, so that no base64 conversion is needed at client side. But again this is a matter of opinion as well.
You can't set the name purely in (1) since at that point you do not know the ID of the product yet.
You can't set the name purely in (2) since at that point the files have already been saved (with filename generated by your filename(req, file, cb) function).
So I think the best solution might be to move the files after they are uploaded.
This could be done in (2). When you process the files in the router, req.files will be an array of files that have already been uploaded.
In your promise callback for Product.create, you have access to the product (which you need for the ID) and the list of files (which you need for the number).
For that, you could use fs.rename(oldPath, newPath, callback).
https://nodejs.org/docs/latest/api/fs.html#fs_fs_rename_oldpath_newpath_callback
Something like this should work:
Product.create(...).then((product) => {
req.files.forEach((file, index) => {
// file.path is the full path to the file that was uploaded.
// newPath is where you want to put it.
// Let's use the same destination and just change the filename.
const newPath = file.destination + product.id + '_' + index
fs.rename(file.path, newPath)
})
})

Fail to upload files from AngularJS to ExpressJs

I am now using angular-file-upload packages to upload files. After I press item.upload(), it claims to be successfully uploaded the file, but I see the req.body is empty. Please Help!
Here is the angular code to handle it:
var uploader = $scope.uploader = $fileUploader.create({
scope: $scope, // to automatically update the html. Default: $rootScope
url: '/api/teams/upload',
formData: [
{ key: 'value' }
],
filters: [
function (item) { // first user filter
$scope.previewImage(item);
return true;
}
]
});
And here is the way to trigger the upload:
uploader.bind('afteraddingfile', function (event, item) {
// console.info(item.file);
console.info('After adding a file', item);
// console.log('item.upload();');
item.upload();
});
And finally here is the express js code:
exports.upload = function(req, res) {
// console.log('req.headers');
// console.log(req.headers);
console.log('req.body');
console.log(req.body);
What wrong's with it?
First make sure your POST is encoded as enctype="multipart/form-data"....
In Express 4 you need to set the body parser in your server:
var bodyParser = require('dy-parser');
//...
var app = express();
//...
app.use(bodyParser()); // pull information from html in POST
var busboy = require('connect-busboy');
app.use(busboy());
In earlier version of Express you only needed to add the body parser from the framework itself and files will be store on the configured location:
app.use(express.bodyParser({limit: '10mb', uploadDir: __dirname + '/public/uploads' })); // pull information from html in POST
Since version 4 removed support for connect now you need to add your custom support for multipart/form data to parser multi/part POSTs, so you will have to to do something like:
var fs = require('fs');
var busboy = require('connect-busboy');
//...
app.use(busboy());
//...
app.post('/api/teams/upload', function(req, res) {
var fstream;
req.pipe(req.busboy);
req.busboy.on('file', function (fieldname, file, filename) {
console.log("Uploading: " + filename);
fstream = fs.createWriteStream(__dirname + '/files/' + filename);
file.pipe(fstream);
fstream.on('close', function () {
res.redirect('back');
});
});
});
On the client side you need to call the $upload.upload To start the upload

Categories

Resources