expressJS/multer multiple file upload, render for each file - node.js

I am using expressJS with multer, and want to create a website to upload multiple files.
So I already manage to get this working. Currently I am using XMLHttpRequest for POST request on the client side, and update elements on page also from the client side script. For 5 files selected to upload with one click on submit button, I can do 5 post request from the client-side and update feedback one by one.
// load file
formData.append(file1);
let req = new XMLHttpRequest();
req.onreadystatechange = function () {
if (req.readyState == XMLHttpRequest.DONE) {
updateView(); // Change layout in HTML when POST DONE
}
}
req.open("POST", "/upload");
req.send(formData);
Reapeat for multiple files [file1, file2, file3,...]
Question:
So now, I would like to use res.render() with parameters instead. I am wondering if it is possible to get one POST request and render the page multiple times. If I POST 5 files at once, I want to render every time one file is processed, and let the user on the client side see the feedback. I don't need a progress bar, I just want to show some basic informations and status of the file. Play a bit around with res.render(), but didn't find anything working as I wished.
So I can avoid adding any HTML code in my JavaScript. And just use the handlebar on the backend.
Front end:
formData.append(file1);
formData.append(file2);
formData.append(file3);
let req = new XMLHttpRequest();
req.open("POST", "/upload");
req.send(formData);
And for backend I want something like this:
router.post('/upload', upload.array('multi_files'), async function (req, res, next) {
const files = req.files;
for (const file of files) {
let result = processFile(file);
res.render('/', result);
}
});
But unfortunately I cannot res.render multiple times.

Related

Sending an Excel file from backend to frontend and download it at the frontend

I had created an Excel file at the backend (Express JS) using Exceljs npm module. I have it stored in a temp directory. Now I would want to send the file from the back-end to the front-end and download it there when the user clicks a button. I am struck on two things
1. How to send the file from the backend to the frontend through an HTTP POST request
2. How to then download the file in the front-end
Edited content:
I need the front end to be a button that appends the file to it and then download it. This is how my code looks, I am not getting the file properly from the backend to the front-end
front end file:
function(parm1,parm2,parm3){
let url =${path}?parmA=${parm1}&parmB=${parm2}&parmC=${parm3};
let serviceDetails = {};
serviceDetails["method"] = "GET";
serviceDetails["mode"] = "cors";
serviceDetails["headers"] = {
"Content-Type": "application/json"
};
fetch(url, serviceDetails)
.then(res => {
if (res.status != 200) {
return false;
}
var file = new Blob([res], { type : 'application/octet-stream' });
a = document.createElement('a'), file;
a.href = window.URL.createObjectURL(file);
a.target = "_blank"; 
a.download = "excel.xlsx";
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
}).catch(error => {
return false;
});
}`
router.js
var abc = ... // this is a object for the controller.js file
router.get('/path', function(req, res) {
abc.exportintoExcel(req, res);
});
controller.js
let xyz = ... //this is a object for the service.js file
exports.exportintoExcel = function(req, res) {
xyz.exportintoExcel(reqParam,res);
}
service.js
exportintoExcel(req,response){
//I have a excel file in my server root directory
const filepath = path.join(__dirname,'../../nav.txt');
response.sendFile(filepath);
})
}
This is a complete re-write of an earlier answer, so sorry if anyone needed that one, but this version is superior. I'm using a project created with express-generator and working in three files:
routes/index.js
views/index.ejs
public/javascripts/main.js
index.ejs
Start with an anchor tag that has the download attribute, with whatever filename you wish, and an empty href attribute. We will fill in the href in the main.js file with an ObjectURL that represents the Excel file later:
<body>
<a id="downloadExcelLink" download="excelFile.xlsx" href="#">Download Excel File</a>
<script type="text/javascript" src="/javascripts/main.js"></script>
</body>
public/javascripts/main.js
Select the anchor element, and then make a fetch() request to the route /downloadExcel. Convert the response to a Blob, then create an ObjectURL from this Blob. You can then set the href attribute of the anchor tag to this ObjectURL:
const downloadExcelLink = document.getElementById('downloadExcelLink');
(async () => {
const downloadExcelResponse = await fetch('/downloadExcel');
const downloadExcelBlob = await downloadExcelResponse.blob();
const downloadExcelObjectURL = URL.createObjectURL(downloadExcelBlob);
downloadExcelLink.href = downloadExcelObjectURL;
})();
routes/index.js
In the index router, you simply need to call the res.sendFile() function and pass it the path to the Excel file on your server.
router.get('/downloadExcel', (req, res, next) => {
const excelFilePath = path.join(__dirname, '../tmp/excel.xlsx');
res.sendFile(excelFilePath, (err) => {
if (err) console.log(err);
});
});
That's it! You can find a git repo here of the project. Clone into it and try it out for yourself if you can't get this code to work in your project as it is.
How It Works
When the page loads, 4 requests are fired off to our server, as we can see in the console output:
GET / 200 2.293 ms - 302
GET /stylesheets/style.css 200 1.123 ms - 111
GET /javascripts/main.js 200 1.024 ms - 345
GET /downloadExcel 200 2.395 ms - 4679
The first three requests are for index.ejs (/), the CSS stylesheet, and our main.js file. The fourth request is sent by our call to fetch('/downloadExcel') in the main.js file:
const downloadExcelResponse = await fetch('/downloadExcel');
I have a route-handler setup in routes/index.js at this route that uses res.sendFile() to send a file from our filesystem as the response:
router.get('/downloadExcel', (req, res, next) => {
const excelFilePath = path.join(__dirname, '../tmp/excel.xlsx');
res.sendFile(excelFilePath, (err) => {
if (err) console.log(err);
});
});
excelFilePath needs to be the path to the file on YOUR system. On my system, here is the layout of the router file and the Excel file:
/
/routes/index.js
/tmp/excel.xlsx
The response sent from our Express server is stored in downloadExcelResponse as the return value from the call to fetch() in the main.js file:
const downloadExcelResponse = await fetch('/downloadExcel');
downloadExcelResponse is a Response object, and for our purposes we want to turn it into a Blob object using the Response.blob() method:
const downloadExcelBlob = await downloadExcelResponse.blob();
Now that we have the Blob, we can call URL.convertObjectURL() to turn this Blob into something we can use as the href for our download link:
const downloadExcelObjectURL = URL.createObjectURL(downloadExcelBlob);
At this point, we have a URL that represents our Excel file in the browser, and we can point the href to this URL by adding it to the DOM element we selected earlier's href property:
When the page loads, we selected the anchor element with this line:
<a id="downloadExcelLink" download="excelFile.xlsx" href="#">Download Excel File</a>
So we add the URL to the href here, in the function that makes the fetch request:
downloadExcelLink.href = downloadExcelObjectURL;
You can check out the element in the browser and see that the href property has been changed by the time the page has loaded:
Notice, on my computer, the anchor tag is now:
<a id="downloadExcelLink" download="excelFile.xlsx" href="blob:http://localhost:3000/aa48374e-ebef-461a-96f5-d94dd6d2c383">Download Excel File</a>
Since the download attribute is present on the link, when the link is clicked, the browser will download whatever the href points to, which in our case is the URL to the Blob that represents the Excel document.
I pulled my information from these sources:
JavaScript.info - Blob as URL
Javascript.info - Fetch
Here's a gif of how the download process looks on my machine:
OK, now that I see your code, I can try and help out a little. I have refactored your example a little bit to make it easier for me to understand, but feel free to adjust to your needs.
index.html
I don't know what the page looks like that you're working with, but it looks like in your example you are creating an anchor element with JavaScript during the fetch() call. I'm just creating one with HTML in the actual page, is there a reason you can't do this?
<body>
<a id="downloadLink" download="excel.xlsx" href="#">Download Excel File</a>
<script type="text/javascript" src="/javascripts/test.js"></script>
</body
With that in hand, here is my version of your front end JS file:
test.js
const downloadLink = document.getElementById('downloadLink');
sendFetch('a', 'b', 'c');
function sendFetch(param1, param2, param3) {
const path = 'http://localhost:3000/excelTest';
const url = `${path}?parmA=${param1}&parmB=${param2}&parmC=${param3}`;
const serviceDetails = {};
serviceDetails.method = "GET";
serviceDetails.mode = "cors";
serviceDetails.headers = {
"Content-Type": "application/json"
};
fetch(url, serviceDetails).then((res) => {
if (res.status != 200) {
return false;
}
res.blob().then((excelBlob) => {
const excelBlobURL = URL.createObjectURL(excelBlob);
downloadLink.href = excelBlobURL;
});
}).catch((error) => {
return false;
});
}
I had to fill in some details because I can't tell what is going on from your code. Here are the things I changed:
Selected the DOM element instead of creating it:
Your version:
a = document.createElement('a'), file;
My version:
index.html
<a id="downloadLink" download="excel.xlsx" href="#">Download Excel File</a>
test.js
const downloadLink = document.getElementById('downloadLink');
This saves us the trouble of creating the element. Unless you need to do that for some reason, I wouldn't. I'm also not sure what that file is doing in your original.
Name the function and change parm -> param for arguments list
Your version:
function(parm1,parm2,parm3){
My version:
function sendFetch(param1, param2, param3) {
I wasn't sure how you were actually calling your function, so I named it. Also, parm isn't clear. Param isn't great either, should describe what it is, but I don't know from your code.
Create a path variable and enclose url assignment in backticks
Your version:
let url =${path}?parmA=${parm1}&parmB=${parm2}&parmC=${parm3};
My version:
const path = 'http://localhost:3000/excelTest';
const url = `${path}?parmA=${param1}&parmB=${param2}&parmC=${param3}`;
In your version, that url assignment should throw an error. It looks like you want to use string interpolation, but you need backticks for that, which I added. Also, I had to define a path variable, because I didn't see one in your code.
Cleaned up some formatting
I used 'dot' notation for the serviceDetails, but that was just personal preference. I also changed the spacing of the fetch() call, but no need to reprint that here. Shouldn't effect anything.
Create a blob from the fetch response
Your version:
var file = new Blob([res], { type : 'application/octet-stream' });
My version:
res.blob().then((excelBlob) => {
I'm not sure why you are calling the Blob constructor and what that [res] is supposed to be. The Response object returned from fetch() has a blob() method that returns a promise that resolves to a Blob with whatever MIME-type the data was in. In an Excel documents case, this is application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.
Create an ObjectURL from the Blob and add this URL to the href of the anchor tag.
Your version:
a = document.createElement('a'), file;
a.href = window.URL.createObjectURL(file);
a.target = "_blank";
a.download = "excel.xlsx";
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
My version:
const excelBlobURL = URL.createObjectURL(excelBlob);
downloadLink.href = excelBlobURL;
You have to do a bunch of DOM manipulation, which I'm not sure why you need. If you do have to dynamically create this element, then I'm not sure why you are 'clicking' it, then removing it, if the user is supposed to be able to click it. Maybe clarify for me why you are doing this, or if you really need to do it. Either way, in my version I create the ObjectURL and then assign it, but you could just as easily not store it in a variable.
Call the function that sends the fetch request.
As my function signature is:
function sendFetch(param1, param2, param3)
I needed to call it somewhere in order to fire off the request, so I did so like this:
sendFetch('a', 'b', 'c');
Right when the page loads, as you can see from the server logs:
GET / 304 0.448 ms - -
GET /javascripts/test.js 304 1.281 ms - -
GET /excelTest?parmA=a&parmB=b&parmC=c 304 0.783 ms - -
The first two requests are for the index.html page and the test.js file, then the fetch request is fired with the param's I passed in. I'm not sure how you are doing this in your app, because that is not included in your code.
Everything I just covered is Front-End. I'm assuming your server-side code is actually sending an excel file with your call to response.sendFile() in service.js. If you are sure that the file is getting sent, then the code I've given you should work, when adjusted to your app.
So, in conclusion, what this code does is:
Load an HTML page with an anchor tag with no href attribute set.
Send off a fetch() request to the server.
Turn the fetch response into a Blob, then create an ObjectURL from this Blob.
Assign that ObjectURL to the anchor tag's href attribute.
When the user clicks the 'Download Excel File' link, the Excel sheet should be downloaded. If you didn't want them to see the link until after the fetch request, you could definitely do create the anchor tag in JS instead, let me know if you want to see how to do that.
As before, here is a gif showing how it looks on my machine (this is with your version and my modifications):

Make data persist in form using mongodb node express

Hi I can connect to and store to mongodb using mlab but I can't get the data in the form to remain on refresh.
The form takes in user input from input-boxes.
Any help would be great.
Thanks
If being able to get the same form data for all the requests (no matter where they are made from)is what you want, then a possible workaround could be as follows:
1) Store the initial form data(probably empty) in the database, get the ObjectId and hard code it in your code, this way all the updates would be made to that specific MongoDB document only.
2) The form route (below one) should do nothing except for serving the file which has the form
app.get('/form1', function (req, res) {
res.sendFile(__dirname + '/public');
//record.find({"_id": ObjectId("MLAB ID")})......dont do this
});
3) There should be another route that sends form data
app.get('/getdata', function(req, res) {
record.find({"_id": ObjectId("MLAB ID")}, function(err, doc) {
res.send(doc)
})
});
4)The static file that you send to the client should have a javascript function such that it asks for the form data as soon as your window loads and then sets the value of input elements from that data recieved:
window.onload = function() {
//make a GET request to /getdata to get form data....store it in obj
//then set the input values
document.getElementById("yourchoice").value = obj.yourchoice;
//and set the value of other input fields ....in similar amnner
}
5) The POST request you make should update that specific document( findOneAndUpdate?? )
Disadvantage of this method:
For one operation we request a file from server and then another request to get form data, so two requests for one operation, since we cannot use both res.sendFile() and res.json() together...one way to get around this is to hide the form data as well in the HTML document using a template engine. More about this can easily be found. Anyways the above method does solve your purpose.
I hope I correctly understood the problem statement.
Happy coding!!
Edit:Although the above mentioned points are explainatory, I have written a sample snippet at : https://pastebin.com/AvgVyx7b
It works perfectly fine

Generating zip archive on-the-fly using Express and node-archiver

I'm trying to generate a zip archive of icons on-the-fly and stream the response to the user to download, via a JSON POST request.
The zip itself is created and the response is returned, but the client-side is not prompted to download the file, and the response is garbled (which I assume is the contents of the archive).
app.post('/download', function(request, response) {
var icons = request.body;
var filename = 'icons.zip';
response.attachment(filename);
var zip = Archiver('zip');
zip.on('finish', function(error) {
return response.end();
});
zip.pipe(response);
for (var i = 0; i < icons.length; i++) {
var icon = getIcon(icons[i]);
zip.append(fs.createReadStream('public/' + icon.svg), { name: icon.title + '.svg' });
}
zip.finalize();
});
I'm wondering if there's anything missing from the server-side code that's preventing the download on the client-side, but from the example I've followed (https://github.com/archiverjs/node-archiver/blob/master/examples/express.js), it doesn't seem to be the case.
Here's some screenshots of the request made and the response received:
AJAX calls don't trigger file downloads in a browser, so you need to work around that.
One possibility is to change the request from a POST to a GET and put the names of the icons in the URL as parameters.
Your Express route would look like this:
app.get('/download/*?', function(request, response) {
// Make sure icons names were provided:
if (! request.params[0]) {
return response.sendStatus(400);
}
// Split on `/`, which is used as separator between icon names:
var icons = request.params[0].split(/\//);
// The rest can be the same
...
});
Client-side, you would use this:
location.href = 'http://your-server/download/Chevron%20Down/Close/Trash';
(obviously you can also generate that URL dynamically based on user input, as long as you make sure that the icon names are properly URL-encoded)

Get MIME type of Node Request.js response in Proxy - Display if image

I’m writing some proxy server code which intercepts a request (originated by a user clicking on a link in a browser window) and forwards the request to a third party fileserver. My code then gets the response and forwards it back to the browser. Based on the mime type of the file, I would like to handle the file server's response in one of two ways:
If the file is an image, I want to send the user to a new page that
displays the image, or
For all other file types, I simply want the browser to handle receiving it (typically a download).
My node stack includes Express+bodyParser, Request.js, EJS, and Passport. Here’s the basic proxy code along with some psuedo code that needs a lot of help. (Mia culpa!)
app.get('/file', ensureLoggedIn('/login'), function(req,res) {
var filePath = 'https://www.fileserver.com/file'+req.query.fileID,
companyID = etc…,
companyPW = etc…,
fileServerResponse = request.get(filePath).auth(companyID,companyPW,false);
if ( fileServerResponse.get('Content-type') == 'image/png') // I will also add other image types
// Line above yields TypeError: Object #<Request> has no method 'get'
// Is it because Express and Request.js aren't using compatible response object structures?
{
// render the image using an EJS template and insert image using base64-encoding
res.render( 'imageTemplate',
{ imageData: new Buffer(fileServerResponse.body).toString('base64') }
);
// During render, EJS will insert data in the imageTemplate HTML using something like:
// <img src='data:image/png;base64, <%= imageData %>' />
}
else // file is not an image, so let browser deal with receiving the data
{
fileServerResponse.pipe(res); // forward entire response transparently
// line above works perfectly and would be fine if I only wanted to provide downloads.
}
})
I have no control over the file server and the files won't necessarily have a file suffix so that's why I need to get their MIME type. If there's a better way to do this proxy task (say by temporarily storing the file server's response as a file and inspecting it) I'm all ears. Also, I have flexibility to add more modules or middleware if that helps. Thanks!
You need to pass a callback to the request function as per it's interface. It is asynchronous and does not return the fileServerResponse as a return value.
request.get({
uri: filePath,
'auth': {
'user': companyId,
'pass': companyPW,
'sendImmediately': false
}
}, function (error, fileServerResponse, body) {
//note that fileServerResponse uses the node core http.IncomingMessage API
//so the content type is in fileServerResponse.headers['content-type']
});
You can use mmmagic module. It is an async libmagic binding for node.js for detecting content types by data inspection.

How to get req and res object of Expreejs in .ejs file

I am trying to use Express js with .ejs views.
I want to redirect my page to some another page on any event let say "onCancelEvent"
As per Express js documentation,I can do this by using res.redirect("/home");
But I am not able to get res object in my ejs file.
Can anyone Please tell me how to access req and res object in .ejs file
Please help.
Thanks
Short Answer
If you want to access the "req/res" in the EJS templates, you can either pass the req/res object to the res.render() in your controller function (the middleware specific for this request):
res.render(viewName, { req : req, res : res /* other models */};
Or set the res.locals in some middleware serving all the requests (including this one):
res.locals.req = req;
res.locals.res = res;
Then you will be able to access the "req/res" in EJS:
<% res.redirect("http://www.stackoverflow.com"); %>
Further Discussion
However, do you really want to use res in the view template to redirect?
If the event initiates some request to the server side, it should go through the controller before the view. So you must be able to detect the condition and send redirect within the controller.
If the event only occurs at client side (browser side) without sending request to server, the redirect can be done by the client side javascript:
window.location = "http://www.stackoverflow.com";
In my opinion: You don't.
It is better to create the logic that determines the need to redirect inside some middleware that happens long before you call res.render()
It is my argument that your EJS file should contain as little logic as possible. Loops and conditionals are ok as long as they are limited. But all other logic should be placed in middleware.
function myFn( req, res, next) {
// Redirect if something has happened
if (something) {
res.redirect('someurl');
}
// Otherwise move on to the next middleware
next();
}
Or:
function myFn( req, res, next) {
var options = {
// Fill this in with your needed options
};
// Redirect if something has happened
if (something) {
res.redirect('someurl');
} else {
// Otherwise render the page
res.render('myPage', options);
}
}

Resources