how to pass JSON data into pug template without render()? - node.js

in my case, the scenario is when i click a button the value of sort, cost range and the type of Cuisine is got through the JS code then i send a post axios request from that and then passing the data from client side to server side ,where i use a controller to get the values of those data, then i send an axios GET request to the Route which is defined in my code to get the information from the database ,the problem which i face is HOW TO SEND the data from my server to frontend pug template because when i use .render() it shows internal server error in my browser but when i use .json() , i can see the output in my console ,so the problem is with my controller function i guess ? so can anyone look into my problem and provide a solution
the controller code is
exports.filter = catchAsync(async (req, res, next) => {
try {
let cost = req.query.cost;
const cuisine = req.body.params.Cuisine;
const Sort = req.body.params.sort;
let paramobj = {};
if (cuisine) {
paramobj.Cuisine = cuisine;
}
if (Sort) {
paramobj.sort = Sort.toString();
}
if (!cost) {
cost = "";
} else {
cost = `?${cost}`;
}
const data = await axios.get(
`http://localhost:3000/api/ver1/restaurant${cost}`,
{
params: paramobj,
}
);
const restaurantinloc=data.data.data
res
.status(200)
.render("restaurantcard", {
restaurantinloc,
});
// .json(restaurantinloc);
} catch (err) {
console.log(err);
}
});
everything works fine when i use res.json(restaurantinloc) but when i use res.render() i shows internal server error and not rendering the page

Related

How to send a NODE.JS post request from an Angular Project?

I have a NODE.JS api using expressjs that connects to an SQL Server, and I want to use it in an angular project. I make use two files, a route file and a controllers file. My route file is as follows:
module.exports = (app) => {
const UsrContrllr = require('../Controllers/users.controllers');
//1. GET ALL USERS
app.get('/api/users', UsrContrllr.func1);
//2. POST NEW USER
app.post('/api/user/new', UsrContrllr.func2);
};
And my controllers file is given below:
const mssql = require('mssql');
exports.func1 = (req, res) =>
{
// Validate request
console.log(`Fetching RESPONSE`);
// create Request object
var request = new mssql.Request();
// query to the database and get the records
const queryStr = `SELECT * FROM USERS`;
request.query(queryStr, function (err, recordset) {
if (err) console.log(err)
else {
if (recordset.recordset.toString() === '') {
res.send('Oops!!! Required data not found...');
}
else {
// send records as a response
res.send(recordset);
}
};
});
};
exports.func2 = (req, res) =>
{
// Validate request
console.log(`INSERTING RECORD ${req}`);
// create Request object
var request = new mssql.Request();
// query to the database and get the records
const queryStr = `INSERT INTO GDUSERS (USERCODE, PASSWORD, LANGUAGE, USERCLASS, FIRSTNAME, LASTNAME, CONTACTNO) VALUES ('${req.body.usercode}', '${req.body.password}', 'EN', '0', '${req.body.firstname}', '${req.body.lastname}', '${req.body.contactno}');`;
request.query(queryStr, function (err, recordset) {
if (err) console.log(err)
else {
if (recordset.recordset.toString() == '') {
res.send('Oops!!! Required data not found...');
}
else {
// Send records as response
res.send(recordset);
}
};
});
};
The GET request works well, but when I try to run the POST request directly from the angular application, I get an error stating
Cannot GET URL/api/user/new
The angular code in my angular project is:
signup() {
let headers = new Headers({ 'Content-Type': 'application/json' });
let options = new RequestOptions({ headers: headers });
console.log(this.user); //User details come from a form
this.http.post(“URL", this.user, options)
.subscribe(
(err) => {
if(err) console.log(err);
console.log("Success");
});
}
I’m not sure whether the angular code I’m using, is right or not, and I don’t know where I’m going wrong. How does one exactly send a http POST request from an Angular project?
this i the way i handled my user signup with http.post calls. my approach is slightly different when signing up user because i am using a promise instead of observable (which i normally use for my servicecalls). but i will show you both ways.
createUser(user: User): Promise < string > {
const promise = new Promise < string > ((resolve, reject) => {
const userForPost = this.createUserForPost(user);
this.http.post(environment.backendUrl + '/api/user/signup', userForPost, this.config).toPromise < HttpConfig > ()
.then(createdUser => {
}).catch(error => {
console.log(error);
});
});
return promise;
}
here another example with an observable
createForumPost(forumPost: ForumPost) {
this.http.post < { message: string, forumPostId: string } > (environment.backendUrl + '/api/forumPosts', forumPost).subscribe((responseData) => {
const id = responseData.forumPostId;
forumPost.id = id;
});
}
i defined my URL somewhere else and then just use the environment.backedUrl + 'path' to define my path (the same as the path in your backend controller)
this is one of my first answers here on SO. i am sry if it is a bit messy
i hope i was able to help with my examples :)

Ejs variable persistence?

I have an express route which filters the month of a blog post and returns the months:
router.get("/months", async (req,res)=>{
try{
let posts = await pool.query(`select * from posts
where MonthName(created_at) = '${req.query.month}'`)
console.log(`select * from posts
where MonthName(created_at) = '${req.query.month}'`)
let tags = await pool.query(`Select tagName from tags`)
filter = req.query.month
res.render("index",{posts,tags,filter})
}
catch(err){
console.log(err)
}
})
It returns the blog posts filter by the month. As you can see I am sending back a filter variable so that in my template I am display the header as in my index.ejs file:
<h2><%= filter %> Blogs</h2>
So that it shows up as April Blogs or whatever filter the user selected
Now the same template is also shated by the default index route:
router.get("/", async (req,res)=>{
try{
let rows = await pool.query(`Select userName as author, posts.* from posts
inner join users on users.id = posts.user_id`)
let tags = await pool.query(`Select tagName from tags`)
res.render("index",{posts:rows,tags:tags})
}
catch(err){
console.log(err)
}
})
By default no filter is applied and a filter variable is not even sent.
Now, the localhost:3000/months/?month=April is perfectly as expected and shows only the blog from April.
But I was expected the localhost:3000 route to throw an error because it is not passing the filter variable but it shows whatever month I selected in the previous route filter.
Only when I terminate the Nodejs server and try to go to the default route do I get:
filter is not defined
But if I go to localhost:3000/months/?month=April and then go back to ``localhost:3000` it loads just fine with month April.
Is it not a good idea to share templates between different routes? How is this possible?
The template is getting cached on first invocation. You might try this answer to disable the view cache in express -
router.disable('view cache');
Another options is listed on the ejs wiki -
router.set('view options', {cache: false});
or you might try disabling in ejs with the additional options parameter -
res.render("index",{posts:rows,tags:tags}, {cache: false})
You need to declare filter and pass a default value, otherwise your renderer will fail.
router.get("/months", async (req,res)=> {
try {
let dbQuery = `select * from posts`;
// handle when req.query.month is not passed.
if (req.query.month) {
dbQuery += ` where MonthName(created_at) = '${req.query.month}'`;
}
const posts = await pool.query(dbQuery);
const dbQuery2 = 'Select tagName from tags';
const tags = await pool.query(dbQuery2);
// declare filter here and assign '' when it's not passed request,
// otherwise your render will fail.
const filter = query.month ? query.month : '';
return res.render("index", {posts, tags, filter});
} catch(err){
// pass error to your main error handler.
return next(err);
}
})
Further i would recommend organising your code better.
// your common shared logic
async function logic({query, params, body}){
const dbQuery = `select * from posts`;
const posts = await pool.query(dbQuery);
return {posts};
}
function handler(req, res, next) {
logic(req)
.then((result) => {
return res.render("index",result);
})
.catch(next);
}
// you can create separate handlers for each requests
// and use the shared logic in handlers.
router.get("/", handler);
router.get("/months", handler);

CSV File downloads from Node Express server

I have an API backend with Node and Express. I am trying to take some filtered data from the frontend and create a CSV file and download it for the user. I have been using json2csv. I am able to create the data file correctly and when I use that file in my express route I download a file that just says undefined. At first, I thought it was an asynchronous issue, but after using a setTimeout as a test to see if that was an issue I still get the undefined data file. Console logging the "csvData" shows the correct data.
Express route to download the file.
app.post('/api/downloads/filtered', (req, res) => {
let fields = [];
fields = Object.keys(req.body[0])
const filteredData = req.body;
const json2csvParser = new json2csv({fields: fields});
const csvData = json2csvParser.parse(filteredData);
console.log(csvData)
fs.writeFile('./report.csv', csvData, (err) => {
if (err) {
console.log(err);
}
else {
console.log('created report.csv');
res.download('./report.csv');
}
})
})
I'm using Vue on the frontend, I get the file when clicking a button not sure if that is something I should include.
I ended up figuring out my issue. I found that downloading in a post request didn't seem to be possible. I needed a get request. Since the data for the file came in the request body I ended up keeping the post request to create the file and creating a separate get request to download the file this seemed to work fine but didn't find it documented anywhere so I wasn't sure if a better way exists.
app.post('/api/downloads/filtered', (req, res) => {
console.log(req.body)
let fields = [];
fields = Object.keys(req.body[0])
const filteredData = req.body;
const json2csvParser = new json2csv({fields: fields});
const csvData = json2csvParser.parse(filteredData);
console.log(csvData)
fs.writeFile('./report.csv', csvData, (err) => {
if (err) {
console.log(err);
}
else {
console.log('created report.csv');
}
})
})
app.get('/api/downloads/filtered', (req, res) => {
setTimeout(() => {res.download('./report.csv')}, 1000)
})

Sending Tweet data to PUG template from within GET method

What's the most efficient way to send data fetched from within a GET function to a PUG template? I've been pushing the data into an array and then sending that to the template, but this seems inefficient.
app.get('/tweet/:id', function(req, res) {
const id = req.params.id;
T.get('statuses/show/' + id, function(err, data, response) {
let stweet = [];
stweet.push(data.text);
});
res.render('tweet', {stweet: stweet});
});
This gets a Tweet ID from the url and uses it to retrieve the Tweet object. Is there some way I can pull "data" from T.get('statuses/show'...) and display it directly on the PUG template?
First of all, your code cannot possible work, since T.get is asyncrhonous & stweet is defined in a scope which res.render('tweet', {stweet: stweet}); does not have access. So that code will throw:
Uncaught ReferenceError: stweet is not defined
T supports promises, you should use them so your code would be a lot cleaner. After T.get is done just send the data directly to the PUG template, there is no need to use: .push
app.get('/tweet/:id', async(req, res) => {
try {
// We wait until `T.get` is done
const { data } = await T.get(`statuses/show/${req.params.id}`, { /** params **/ });
res.render('tweet', { stweet: [data.text] });
} catch(e) {
// Or whatever error message you want to display
res.render('error');
}
});
You should check: How do I return the response from an asynchronous call?

Express breaking when getting concurent client calls

Because of an error that I had in the client, my node / express API was getting the exact same PUT request twice simultaneously everytime that a form was sent.
In response, the server was always crashing with the following error:
Error: Can't set headers after they are sent
Now I fixed the client so server is not crashing anymore, but I'd like to fix the API properly. Anyone could kill my server by sending two PUT requests simultaneously.
So my question:
How to best handle the "can't set headers" error with Express?
What is the mistake in my server code that allowed for that error to happen in the first place?
Here is my server code:
server.js
router.route('/deals/:id').put(deal.update);
deal.js
var r = {};
exports.update = function(req, res) {
r = res;
var u = {
$set: {}
}
for(var x in req.body){
if (['name','stages'].indexOf(x)>=0)
u.$set[x] = req.body[x];
}
Pipeline // this is a mongoose object
.findOneAndUpdate({'_id.id':req.params.id},u,{new:true})
.exec(respond);
}
function respond(err, data) {
if (err) {
return r.send(err);
} else {
return r.json(data);
}
}
UPDATE:
The error came from how I declared a global r in order to have a single response callback outside for all of my methods.
I fixed it by having one respond callback for every method.
Still interested to know if I could get away with a bind(res) instead?
You missed a return, so that when there was an error, you called res.send twice. With the return, execution will stop after the first one.
I see what you are trying to do for your follow on question, but making a global variable r is definitely not a good way to go. The way I do patterns like this is to pass res to the follow on function - see below. There may be a better way, but this seems legitimate at least.
exports.update = function(req, res) {
var u = {
$set: {}
}
for(var x in req.body){
if (['name','stages'].indexOf(x)>=0)
u.$set[x] = req.body[x];
}
Pipeline // this is a mongoose object
.findOneAndUpdate({'_id.id':req.params.id},u,{new:true})
.exec(function(err, data) { respond(err, data, res) });
}
function respond(err, data, res) {
if (err) {
return res.send(err);
}
return res.json(data);
}

Resources