Can app.render() render an array of views? - node.js

It does not seem to be documented, I was wondering if it is possible to render multiple views or an array of views in Expressjs like so:
const data = 'some data passed by a DB';
const app = express();
const arrayViews = ['layout','email', 'web'];
app.render(arrayViews, data, (err, html) => {
if (err) throw err;
})
or do I have to do it in multiple instances
app.render('email', data, (err, html) => {
if (err) throw err;
})
app.render('web', data, (err, html) => {
if (err) throw err;
})

No. You cannot pass an array of views to app.render(). The point of app.render() is to create ONE piece of rendered HTML that can be sent as a response to a particular http request. You can't send multiple responses to one http request. So, you only want to call res.render() once for any given request.
If you want to have two different types of responses for different situations, then you should either make a separate route for each response and call res.render() once in each response with the appropriate template for that request.
Or, you can pass in a query parameter in the URL and use an if statement in the route handler to decide which template to render. But, the point is that you send exactly one response for each request.
For example, here's looking at a query parameter to decide which type of render to do:
app.get('/mypage', function(req, res) {
if (req.query.page === "email") {
res.render('email', data);
} else {
res.render('web', data);
}
});
Otherwise, you'd have two separate routes:
app.get('/mypage/email', function(req, res) {
res.render('email', data);
});
app.get('/mypage/web', function(req, res) {
res.render('web', data);
});
I may have originally been confused about the point of your question (since you don't show the overall context of your render or what you're using it for).
If you're using app.render() to collect the HTML from rendering operations, you can call that multiple times and wait for all to be done and then you have multiple rendered templates which you can do whatever you want with. But, a single app.render() doesn't accept multiple templates. You'd have to call it multiple times and wait for all the requests to be done.
Promise.all([appRender('email'), appRender('web')]).then([email, web] => {
// can access both rendered templates here
// to do with them whatever you want to do
}).catch(err => {
// error here
});
Or, you could use this to make a version of app.render() that would take an array:
const util = require('util');
const appRender = util.promisify(app.render.bind(app));
app.renderAll = function(arrayOfTemplates) {
return Promise.all(arrayOfTemplates.map(template => {
return appRender(template);
}));
});
app.renderAll(['email', ['web']]).then([email, web] => {
// can access both rendered templates here
// to do with them whatever you want to do
}).catch(err => {
// error here
});

Related

Nodejs global variable scope issue

I'm quite new to Nodejs. In the following code I am getting json data from an API.
let data_json = ''; // global variable
app.get('/', (req, res) => {
request('http://my-api.com/data-export.json', (error, response, body) => {
data_json = JSON.parse(body);
console.log( data_json ); // data prints successfully
});
console.log(data_json, 'Data Test - outside request code'); // no data is printed
})
data_json is my global variable and I assign the data returned by the request function. Within that function the json data prints just fine. But I try printing the same data outside the request function and nothing prints out.
What mistake am I making?
Instead of waiting for request to resolve (get data from your API), Node.js will execute the code outside, and it will print nothing because there is still nothing at the moment of execution, and only after node gets data from your api (which will take a few milliseconds) will it execute the code inside the request. This is because nodejs is asynchronous and non-blocking language, meaning it will not block or halt the code until your api returns data, it will just keep going and finish later when it gets the response.
It's a good practice to do all of the data manipulation you want inside the callback function, unfortunately you can't rely on on the structure you have.
Here's an example of your code, just commented out the order of operations:
let data_json = ''; // global variable
app.get('/', (req, res) => {
//NodeJS STARTS executing this code
request('http://my-api.com/data-export.json', (error, response, body) => {
//NodeJS executes this code last, after the data is loaded from the server
data_json = JSON.parse(body);
console.log( data_json );
//You should do all of your data_json manipluation here
//Eg saving stuff to the database, processing data, just usual logic ya know
});
//NodeJS executes this code 2nd, before your server responds with data
//Because it doesn't want to block the entire code until it gets a response
console.log(data_json, 'Data Test - outside request code');
})
So let's say you want to make another request with the data from the first request - you will have to do something like this:
request('https://your-api.com/export-data.json', (err, res, body) => {
request('https://your-api.com/2nd-endpoint.json', (err, res, body) => {
//Process data and repeat
})
})
As you can see, that pattern can become very messy very quickly - this is called a callback hell, so to avoid having a lot of nested requests, there is a syntactic sugar to make this code look far more fancy and maintainable, it's called Async/Await pattern. Here's how it works:
let data_json = ''
app.get('/', async (req,res) => {
try{
let response = await request('https://your-api.com/endpoint')
data_json = response.body
} catch(error) {
//Handle error how you see fit
}
console.log(data_json) //It will work
})
This code does the same thing as the one you have, but the difference is that you can make as many await request(...) as you want one after another, and no nesting.
The only difference is that you have to declare that your function is asynchronous async (req, res) => {...} and that all of the let var = await request(...) need to be nested inside try-catch block. This is so you can catch your errors. You can have all of your requests inside catch block if you think that's necessary.
Hopefully this helped a bit :)
The console.log occurs before your request, check out ways to get asynchronous data: callback, promises or async-await. Nodejs APIs are async(most of them) so outer console.log will be executed before request API call completes.
let data_json = ''; // global variable
app.get('/', (req, res) => {
let pr = new Promise(function(resolve, reject) {
request('http://my-api.com/data-export.json', (error, response, body) => {
if (error) {
reject(error)
} else {
data_json = JSON.parse(body);
console.log(data_json); // data prints successfully
resolve(data_json)
}
});
})
pr.then(function(data) {
// data also will have data_json
// handle response here
console.log(data_json); // data prints successfully
}).catch(function(err) {
// handle error here
})
})
If you don't want to create a promise wrapper, you can use request-promise-native (uses native Promises) created by the Request module team.
Learn callbacks, promises and of course async-await.

ERR_HTTP_HEADERS_SENT: Cannot set headers after they are sent to the client at ServerResponse

I am trying to create a simple REST API with NodeJS and Express without any database. I have stored all of my data in JSON files.
The data is in the form of an array of objects.
I have paths like fund-name/:portId
so I am doing this:
const fundName = require('./json/fund-name.json');
app.get('/fund-details:portId', (req, res) => {
const portId = req.params.portId;
fundDetails.forEach(fund => {
if (fund.portId === portId) {
return res.json(fund);
}
return res.json([]);
});
});
when I hit the url http:localhost:3000/fund-details/1234, I get the following error:
Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent
to the client
at ServerResponse.setHeader (_http_outgoing.js:470:11)
at ServerResponse.header (/home/username/Desktop/data-server/node_modules/express/l
ib/response.js:767:10)
It works fine when I don't pass any path param to get all the funds.
Where am I going wrong??
This error is because you are using res.send() multiple time in single api call.
Correct way
if(a){
res.send()
}else{
res.send()
}
Wrong way
if(a){
res.send()
res.send()
}else{
res.send()
}
In your code.
app.get('/fund-details:portId', (req, res) => {
const portId = req.params.portId;
fundDetails.forEach(fund => {
if (fund.portId === portId) {
return res.json(fund); // many or single times here
}
return res.json([]); // and here when fund !==portId here
});
});
You can try
app.get('/fund-details:portId', (req, res) => {
const portId = req.params.portId;
var flag
var data = []
fundDetails.forEach(fund => {
if (fund.portId === portId) {
flag=true
data.push(fund)
}
});
if(flag){
res.send(data);
}else{
res.send()
}
});
The method res.json(fund) is called per each item in fundDetails and then a further res.json([]) method is called. This leads to your response being send back multiple times (which it shouldn't happen, only 1 response per a single api call should be used).
I suggest that you use an array and push back objects with the matching port id and then send the array back to the user when the operation is completed. To be honest, you don't even need the flag variable to check if funds exists or not since if they don't, you empty data array is sent back.
var data = [];
fundDetails.forEach(fund => {
if (fund.portId === portId)
data.push(fund);
});
res.json(data);

Does order matter in REST api express, ie. get -> post -> put -> delete?

Does it matter if you implement the get method before another method such as post for example implement app.post() before app.get()? I am not sure why there would be significance in changing the order, but in the express app that I built if I implemented post before get, my data would buffer and then be posted every other call, the posting was inconsistent. When I switched the order the issue was fixed.
This is the code for the requests
const xhrPost = new XMLHttpRequest();
const xhrGet = new XMLHttpRequest();
//sends data to DB
xhrPost.open("POST", '/endgame', true);
xhrPost.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
xhrPost.send(JSON.stringify({
playerScore: score
}));
//when data is done being posted, get list of scores from db
xhrPost.onreadystatechange = function() {
console.log(this.responseText);
if (this.readyState === 4 && this.status === 200) {
xhrGet.open("GET", '/endgame', true);
xhrGet.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
xhrGet.send();
}
}
//when scores retrieved display results on console
xhrGet.onreadystatechange = function() {
if (this.readyState === 4 && this.status === 200) {
console.table(JSON.parse(this.responseText));
var data = (JSON.parse(this.responseText));
ctx.fillText(data[0].playerScore, 50, 150);
}
};
and this is the server side code
mongodb.MongoClient.connect(url, (error, database) => {
if (error) return process.exit(1)
const db = database.db('js-snake-scores')
app.post('/endgame', (req, res) => {
let score = req.body
db.collection('scores')
.insert(score, (error, results) => {
if (error) return
res.send(results)
})
})
app.get('/endgame', (req, res) => {
db.collection('scores')
.find({}, {
playerScore: 1
}).toArray((err, data) => {
if (err) return next(err)
res.send(data)
})
})
app.use(express.static(path.join(__dirname, 'static')))
app.listen(process.env.PORT || 5000)
})
Does it matter if you implement the get method before another method such as post for example implement app.post() before app.get()?
No. Order matters only when two routes would handle both the same path and the same method. So, since app.post() and app.get() each only intercept different methods, they don't compete in any way and thus their relative ordering to each other does not matter. Only one will ever trigger on a GET and only the other one will ever trigger on a POST regardless of their order of definition.
If you saw a difference in behavior due to the order, then it must have been due to some other effect besides just an app.get() and an app.post() with the same path because those two are not ever activated on the same request. If we could see the two implementations of code where you say order mattered when you switched them, then we could likely offer you a better idea of why you saw a difference in behavior. app.post() and app.get() ordering by themselves would not cause what you described.

Error: Can't set headers after they are sent Braintree

I am currently working on an admin panel for this website I am creating, so I am able to accept payments via Braintree but I need to implement the ability to retrieve a customers transactions but once a header is sent it sends just one of them and not the whole thing. Is it possible to combine the json to an array so it will send in the one header?
CODE:
router.get('/:cid/test', function(req, res) {
var stream = gateway.transaction.search(function (search) {
search.customerId().is(req.params.cid);
}, function (err, response) {
response.each(function (err, transaction) {
return res.render('admin/test', {transaction: transaction});
});
});
});
This is solely following the Braintree documentation and I know exactly why the error occurs. Any help is really appreciated and I am terrible at explaining so if you need to know more information please give me a holler!
UPDATE: So, I figured I would explore another method and I noticed the 'response' gives back an array of ids. So I will just use EJS to loop through all those and then have a seperate page for each transaction.
Disclaimer: I work for Braintree :)
As Robert noted, you can only call res.render (or any of the response methods that end the request) once per request (hence the error from express).
Unfortunately, you cannot treat response as an array, so you will need to use
one of the two documented ways of interacting with search responses. I personally prefer the streams approach because it is clearer:
app.get('/stream', function (req, res) {
var transactions = []
var transactionStream = gateway.transaction.search(function (search) {
search.customerId().is(req.params.cid);
})
transactionStream.on('data', function (transaction) {
transactions.push(transaction)
})
transactionStream.on('error', function () { /* handle errors */ })
transactionStream.on('end', function () {
res.json({transactions: transactions});
})
})
Alternately, you can use the ids property of response to compare the transactions array that you build from each to know when to end the request:
app.get('/calback', function (req, res) {
var transactionStream = gateway.transaction.search(function (search) {
search.customerId().is(req.params.cid);
}, function (err, response) {
var transactions = []
response.each(function (err, transaction) {
transactions.push(transaction)
if (transactions.length === response.ids.length) {
res.json({transactions: transactions});
}
})
})
})
You can only render one response per route. So you can only call this once and not in a loop:
res.render('admin/test', {transaction: transaction}); });
You can use the each method to iterate through the response and build up a result:
var transactions =[];
response.each(function (err, transaction) { transactions.push(transaction) });
return res.render('admin/test', {transaction: transactions});
That would work if the each method is synchronous. If it's not (and Nick would know), use the solution below.

rendering JSON in view

I am writing an app in node.js, I have the following code.
API for retrieving topic from DB
allTopics = function (req, res) {
db.Topic.all({limit: 10}).success(function (topics) {
res.send(topics)
});
};
Route for topics index
app.get('/topics', function (req, res){
res.render('topics/index.ejs',{ topics : allTopics })
});
Is the above code correct for route?
Also I have index.ejs file where I want to list all the topics (i.e. retrieve data from json response). How do I achieve this?
Your code as-is won't work but you could rewrite it as follows:
// notice how I am passing a callback rather than req/res
allTopics = function (callback) {
db.Topic.all({limit: 10}).success(function (topics) {
callback(topics);
});
};
// call allTopics and render inside the callback when allTopics()
// has finished. I renamed "allTopics" to "theData" in the callback
// just to make it clear one is the data one is the function.
app.get('/topics', function (req, res){
allTopics(function(theData) {
res.render('topics/index.ejs',{ topics : theData });
});
});

Resources