Send PDF as response to client - node.js

I'm facing a strange behaviour with PdfKit. I'm using Nodejs and Express. When I call my route that generate the PDF, the route itself is called twice, and I don't understand why.
Below is the smallest code that recreate this:
var express = require('express'),
app = express();
app.get('/', function (req, res) {
console.log('Route called with referer', req.headers.referer);
var PdfDocument = require('pdfkit'),
doc = new PdfDocument();
doc.pipe(res);
doc.addPage();
doc.end();
});
app.listen(7373, function () {
console.log('started');
});
In the terminal, I have these logs, refreshing only one time the page from the browser:
node tmp/server.js
started
Route called with referer undefined
Route called with referer http://127.0.0.1:7373/
Anyone knows why the route is called one more time automatically?

Ok, after some analysis, I found that it's the browser's PDF viewer that launch a second call. When using wget or curl, I see only one call and one log. So just be aware that code is parsed twice when diplaying the page from the browser.

Related

How to process JS file returned from Express response.sendFile()

I have an API which uses Node.js + Express on the backend.
For one of the API endpoints, I'd like to use the Express response object method of "sendFile", documented here:
https://expressjs.com/en/api.html#res.sendFile
The API should return a Javascript file through the sendFile method.
What I can't figure out is how to read in the .js file on the front end so that I can use the JavaScript functions defined in the file. The sendFile portion appears to be working -- it's just the use of the file which I can't figure out.
Here's what I'm doing on the backend:
app.get("/api/member", async (req, res) => {
options = {
root: path.join(__dirname, '/static'),
dotfiles: 'deny'
}
res.sendFile("member.js", options, (err) => {
if (err) {
console.log(err)
next(err)
} else {
console.log('Sent file')
}
})
});
This seems to be working fine, as I can navigate to the endpoint on my localhost and it loads the JS file. The file member.js simply contains some javascript function definitions.
But, I can't figure out how to consume/use the file once it arrives to the front end.
Here's what I have currently on the frontend:
async function refreshJS() {
const url = `${baseUrl}/member`;
const response = await fetch(url, { credentials: "include" });
const script = document.createElement("script")
script.type = "text/javascript"
script.src = response.body
document.head.appendChild(script)
eval(script)
}
I've spent a lot of time looking through the console/debugger to find the text associated with the JS functions -- but they're nowhere to be found.
I've tested this general framework by loading JS files locally through the console and it worked, so I think it's wrapped up in a misunderstanding of where the JS functions live in the API response. For example, if I replace the command above of:
script.src = response.body
with
script.src = "member.js"
then everything works fine provided I have the file locally.
The examples that I've reviewed seem to deal exclusively with sending an HTML file which is loaded on the frontend. But, I can't find supporting documentation from the fetch API to understand how to use the JS file contents.

Onclick function pug from node

I passed function to render in my pug file. What I want is when the button is clicked, the function should start, but the function starts when I enter the site. Below is how I passed function and using this in PUG file
Router
const start = require('../bot')
exports.home = (req, res) => {
res.render('home', {
functionOne: start.mainFunction()
});
}
PUG File
html
head
link(rel="stylesheet" href="https://fonts.googleapis.com/css?family=Montserrat:300,400,500,700")
link(rel="stylesheet" href="/css/main.css")
body
button(type="button" id="mybutton" onclick="functionOne")
The res.render statement is only meant to send data to the template, you can't send a function through there. The Express documentation explains this.
The function is running when you request the page because that's actually what you're telling it to do when you include that function in there.
You will need to write some client-side JavaScript to make an AJAX call or form POST then create a new ExpressJS route on the server to capture that and run the function.

Unable to use res.send and res.download in Node/Express due to headers already being set

I am new to Node, and I am trying to make it so that when I go to 'localhost:1337/download/open' it renders a webpage, as well as download a file.. I understand that you can only set a header once (that is the error I am getting), but what is the easiest way to both render html AND download a file? Code below:
const express = require('express');
const app = express();
app.get('/download/open', function (req, res) {
let file = `${__dirname}/downloads/Open Tasks.csv`;
res.download(file);
res.send("words");
})
app.listen(1337, function (err) {
if (err) {
console.log(err)
return
}
console.log(`App running. listening on: http://localhost:1337`);
});
Error:
Error: Can't set headers after they are sent.
Thank you in advance.
I was able to figure out what I was trying to do. Instead of trying to render a whole new page AND download a file, I needed to dedicate a route to just a download through the use of an <a></a> tag.
For instance, if I have a webpage at 'http://localhost:1337' that has a link on it like:
Download Open Tasks
Download Open Tasks
Then in node.js I have a route for 'download/open' like so:
app.get('/download/open', function (req, res) {
let file = `${__dirname}/downloads/Open Tasks.csv`;
res.download(file);
})
It will not open a new page (like I thought it needed to) it will just download the file.
IMO, I would suggest you should do the following to achieve your goal:
render the HTML result for "GET http://localhost:1337/download/open"
In the HTML file /download/open, put AJAX block to invoke download file operation
(Download a file by jQuery.Ajax)
$(document).ready(function(){
//code to invoke download file....
});

NodeJS exports not defined in external JS file

I am calling a function from an external JS file in my routes file. So, I am trying to export a function from my externalFile.js file. However, when I run the node server, it throws Uncaught ReferenceError: exports is not defined . externalFile.js is in public/javascripts/ and the route files is in routes/.
External file (externalFile.js):
exports.capsolvingComplete = function (stdout){
//Received, display text and hide the spinner, put check in place.
$('#capsolv-complete').css("display", "block")
$('#capsolv-output').text(stdout);
}
Route file:
var express = require('express');
var router = express.Router();
var script = require('../public/javascripts/externalFile.js')
/* GET home page. */
router.get('/', function(req, res) {
res.render('index', { title: 'CapSolv' });
});
router.post('/file-upload', function(req, res){
script.capsolvingComplete("HI");
res.end("success");
})
module.exports = router;
Thank you for your help! Bit of a node noob here.
Looking at your example, mscdex is right. Anything you require in Node is designed to run on the server, and doesn't have access to the browser's DOM.
I would recommend building a REST API that accepts the file upload, stores it, and returns a JSON object containing metadata: i.e. where the file was stored, size, etc., and let your client deal with that.

Cascade-like rendering with Express JS

With an express app running on a node server, how would I go about recursively searching for a render file from the full path right back to the beginning of the supplied URL.
For example, if someone was to hit my server with www.somewebsite.com/shop/products/product, the render engine would first check that there is an index.jade file in shop/products/product/. If none is found it would then check shop/products/, and subsequently shop/.
var express = require('express');
var app = express();
app.get('/*', function(req, res){
res.render(req.path + '/index.jade', function(err, html){
// some loopback code which alters the path and recalls the render method
})
});
The problem is that the response object is not passed to the render callback, so I'm unable to recall render on the response. I'm looking to create a loop because the URL paths may be any number of directories deep, so I can't just assume I only need to cascade for a definitive number of times.
Anyone see a way round this?
You should be able to use the response object from the closure. I think (assuming express allows you to call res.render a second time) you could use code like this answer to achieve what you want:
var express = require('express');
var app = express();
app.get('/*', tryRender);
function tryRender(req, res){
res.render(req.path + '/index.jade', function(err, html){
if (err) {
req.path = 'mynewpath';
tryRender(req, res);
}
})
}
Note: You will need to add a base case or this function will recurse infinitely if it doesn't find a view that works :D
In the event that express doesn't allow a subsequent call to res.render, you'll probably need to find out if the file exists on the file system yourself.

Resources