NODEJS, PUG, EXPRESS - Cannot read property 'length' of undefined - node.js

I have an API that i tweaked to make 2 calls to my DB and pull down information .. I can see the results in my console log so i know it is working
The next part is when it renders the view i need to show the results in two places
Here is the code for the API that makes 2 calls to the DB
function apples(req, res, next) {
sql.connect(config, function () {
var request = new sql.Request();
request.query("select price from table WHERE fruit = 'apples'", function(err, recordsetapples) {
var arrayLength = recordsetapples.length;
for (var i = 0; i < arrayLength; i++) {
console.log(recordsetapples[i]["price"]);
};
res.render('index', { resultsapples: recordsetapples });
return next();
});
});
};
function pear(req, res, next) {
sql.connect(config, function () {
var request = new sql.Request();
request.query("select price from table WHERE fruit = 'pear'", function(err, recordsetpear) {
var arrayLength = recordsetpear.length;
for (var i = 0; i < arrayLength; i++) {
console.log(recordsetpear[i]["price"]);
};
res.render('index', { resultspear: recordsetpear });
next();
});
});
};
app.get('/fruit', apples, pear);
So after that runs I can see the price print in console log .. Then i see this error
Cannot read property 'length' of undefined
What i expect to see if the price appear ... To get that info i have this code
tr
th.hidden-phone Fruit
th.hidden-phone Price
tr
each val in resultsapples
td.hidden-phone Apples
td.hidden-phone !{val.price}
tr
each val in resultspear
td.hidden-phone Pears
td.hidden-phone !{val.price}

The problem is your view expects both lists at the same time but you attempt to render the view twice with each list separately, which means in either scenario one list in the view will be undefined.
Even if you were to fix this, this approach won't work anyway because after the first res.render the HTTP response will end and return to the client. Ideally you would want to make one trip to the DB for both resultsets and then render the view e.g.
sql.connect(config, () => {
const request = new sql.Request();
request.query("select price from table WHERE fruit = 'apples' OR fruit = 'pear'", (err, result) => {
res.render('index', {
resultsapples: result.recordsets[0],
resultspear: result.recordsets[1]
});
});
});

As James mentioned your callbacks are async so you're trying to render the view twice. You also need some error handling in your sql functions
function apples(cb) {
sql.connect(config, function () {
var request = new sql.Request();
request.query("select price from table WHERE fruit = 'apples'", function(err, recordsetapples) {
if(err) {
return cb(err);
}
var arrayLength = recordsetapples.length;
for (var i = 0; i < arrayLength; i++) {
console.log(recordsetapples[i]["price"]);
};
cb(false, recordsetapples);
});
});
};
function pear(cb) {
sql.connect(config, function () {
var request = new sql.Request();
request.query("select price from table WHERE fruit = 'pear'", function(err, recordsetpear) {
if(err){
return cb(err)
}
var arrayLength = recordsetpear.length;
for (var i = 0; i < arrayLength; i++) {
console.log(recordsetpear[i]["price"]);
};
cb(false,recordsetpear);
});
});
};
app.get('/fruit', (req,res) => {
apples((appleerr,appleset) => {
if(appleerr){
//render error page
} else {
pear((pearerr, pearset) => {
if(pearerr) {
//render error page
} else {
return res.render('index', {
resultapples: appleset,
resultpears: pearset
});
}
})
}
});
});
Now for the record, I'm not a fan of nesting the callbacks like this so I would actually recommend you look at Promises and/or async/await but I'm not sure on your coding level so I didn't want to throw too many concepts at you at once.
Also whereas James has merged your SQL statements into one (which is probably the right approach for you) I kept them separate not knowing if you were reusing these individual pieces of code elsewhere and as such didn't want to combine them.
If you are interested in the promise implementation it might look as follows:
function apples() {
return new Promise((resolve,reject) => {
sql.connect(config, function () {
var request = new sql.Request();
request.query("select price from table WHERE fruit = 'apples'", function(err, recordsetapples) {
if(err) {
reject(err);
}
var arrayLength = recordsetapples.length;
for (var i = 0; i < arrayLength; i++) {
console.log(recordsetapples[i]["price"]);
};
resolve(recordsetapples);
});
});
};
function pear() {
return new Promise((resolve,reject) => {
sql.connect(config, function () {
var request = new sql.Request();
request.query("select price from table WHERE fruit = 'pear'", function(err, recordsetpear) {
if(err){
reject(err)
}
var arrayLength = recordsetpear.length;
for (var i = 0; i < arrayLength; i++) {
console.log(recordsetpear[i]["price"]);
};
resolve(recordsetpear);
});
});
});
};
app.get('/fruit', (req,res) => {
var applePromise = apples()
var pearsPromise = applePromise.then((appleSet)) {
return pear()
}
Promise.all([applePromise,pearsPromise]).then((([appleSet,pearSet]) => {
res.render('index', {
resultapples: appleSet,
resultpear: pearSet
});
}).catch((err) => {
//render error
})
});

Related

Sqlite .all() function returns a promise, but I need the database items...?

I'm new to NodeJS, and I'm trying to learn it by building a Shopping cart web app. I'm storing the data in an SQLite database but I can't seem to access it. The .all() method returns a Promise object and I can't figure out how to obtain the items in the database instead.
I followed this tutorial: https://stackabuse.com/a-sqlite-tutorial-with-node-js/ to build a data access object and two models: ItemRepository and CartRepository.
This is the get method in my data access object script:
get(sql, params = []) {
return new Promise((resolve, reject) => {
this.db.get(sql, params, (err, result) => {
if (err) {
console.log('Error running sql: ' + sql)
console.log(err)
reject(err)
} else {
resolve(result)
}
})
}
And this is my index.js
router.get('/', function(req, res, next) {
var dao = new AppDAO('./database.sqlite3');
var itemRepo = new ItemRepository(dao);
var cartRepo = new CartRepository(dao);
items = itemRepo.getAll();
console.log(items);
var itemRows = [];
var rowSize = 3;
for (var i = 0; i < items.length; i += rowSize){
itemRows.push(items.slice(i, i+rowSize));
}
res.render('shop/index', { title: 'sHOP', items: itemRows })
});
I'm a little lost as for how to get the table's content and not the Promise object.
I will recommend you to read promises and async/await or bluebird promises. you will need to use then method on promise to get the actual result as below.
router.get('/', function(req, res, next) {
var dao = new AppDAO('./database.sqlite3');
var itemRepo = new ItemRepository(dao);
var cartRepo = new CartRepository(dao);
itemRepo.getAll().then(( items) =>{
console.log(items);
var itemRows = [];
var rowSize = 3;
for (var i = 0; i < items.length; i += rowSize){
itemRows.push(items.slice(i, i+rowSize));
}
res.render('shop/index', { title: 'sHOP', items: itemRows })
})
});

Pass variable from app.get to pug view

I have the below code that make a call to my DB and pulls down info
// GET the number of apples left in stock
app.get('/apples', function (req, res) {
sql.connect(config, function() {
var request = new sql.Request();
request.query("select quantity from table WHERE type = 'apple'", function(err, recordset) {
var arrayLength = recordset.length;
for (var i = 0; i < arrayLength; i++) {
console.log(recordset[i]["quantity"]);
res.render('index', {results: recordset});
};
});
})
})
this works perfectly .. when i browse to that it sends me to the index page and then i can see the values from the DB spit out in my console log
In my index.pug I then have this
h3(align='middle') #{results}
On the index page I just see this [object Object]
First off all you shouldn't call res.render multiple times in the for loop.
app.get('/apples', function (req, res) {
sql.connect(config, function () {
var request = new sql.Request();
request.query("select quantity from table WHERE type = 'apple'", function (err, recordset) {
var arrayLength = recordset.length;
for (var i = 0; i < arrayLength; i++) {
console.log(recordset[i]["quantity"]);
};
res.render('index', { results: recordset });
});
});
});
Then you should use each in your pug file to iterate resultset.
More doc for iteration : https://pugjs.org/language/iteration.html
each val in results
h3(align='middle')=val.quantity

using express to loop through a mongoose schema function ,collecting data into an array and rendering the page finally with all the data

Nodejs +Express+Mongoose beginner's question:
*So I came across this problem where I had an array of categories (clothes category). For each category, I wanted to fetch results from the database. So I did what a normal person would do. I used a for loop, fetched the product, stored in an array, then fetched next product, stored in into the same array and so on....... (same process for men and women both ). Now i want to render my view with the data but it isn't accessible coz of the asynchronous nature of node js. I totally understand it. So what I am looking for is an alternative/solution for my problem *
My express code explains my agony better. Please have a look:
//INDEX PAGE
router.get('/', (req, res, next) => { //anonymous callback function
let allCategories;
let womenFashion = [],
menFashion = [];
Clothing.groupByBodyPart(function(err, categories) {
if (categories) {
allCategories = categories.slice(0);
for (let i = 0; i < allCategories.length; i++) { //looping
let category = allCategories[i]._id; //gives a category, eg.,footwear,bottomwear
Clothing.getCategoryWise(category, 'M', function(err, products) { //products here will be an array of objects
if (products) {
menFashion[i] = products.slice(0); //storing products into an array for future use
console.log(menFashion[i]); //accessible here one at a time Eg.,[{slippers},{shoes}]
}
});
Clothing.getCategoryWise(category, 'F', function(err, products) {
if (products) {
womenFashion[i] = products.slice(0); //same as above
console.log(womenFashion[i]); //same as above
}
});
}
}
console.log(menFashion[0]); // not accessible here, so I can't render my page
res.render('index.pug', {
allCategories,
menFashion,
womenFashion
}); //won't work as menFashion and womenFashion aren't available
});
});
Here you go with the mongoose static function:
//get categorywise products
clothingSchema.statics.getCategoryWise = function(bodyPart,gender,callback){
Clothing.aggregate([
{ $match: {'category.bodyPart': bodyPart,
'category.gender': gender
}
},
{ $group: {_id: "$category.type" }
},
{ $sort: {_id: 1 }
}
])
.exec((err,products)=>{
if(err){
return callback(err);
}else if(!products){
let err = new Error('No Product Found!');
err.status = 401;
return callback(err);
}
return callback(null,products);
});
}
Just for the record
Everything is working great, I am just having trouble rendering my page because the menFashion and womenFashion array aren't accesible outside the callback.
A thanks in advance :)
UPDATE:
I solved it myself,but still thnks guys (specially #faiz)
My solution basically includes nesting:
//INDEX PAGE
router.get('/',(req,res,next)=>{ //anonymous callback function
let allCategories;
let womenFashion = [], menFashion = [];
Clothing.groupByBodyPart(function(err,categories){
if(categories){
allCategories = categories.slice(0);
for(let i = 0; i < allCategories.length; i++){ //looping
let category = allCategories[i]._id; //gives a category, eg.,footwear,bottomwear
Clothing.getCategoryWise(category,'M',function(err,products){ //products here will be an array of objects
if(products){
menFashion.push(products);
menFashion[i] = products.slice(0); //storing products into an array for future use
//console.log(menFashion[i]); //accessible here on at a time Eg.,[{slippers},{shoes}]
}
Clothing.getCategoryWise(category,'F',function(err,products){
if(products){
womenFashion[i] = products.slice(0); //same as above
//console.log(womenFashion[i]); //same as above
}
if(i == allCategories.length-1){
console.log('men',menFashion); //everything accessible
console.log('men',menFashion); //everything accessible
res.render('index.pug',{allCategories,menFashion,womenFashion}); //tadaaaaaaaa
}
});
});
}
}
});
});
You can use something like this.
I used two Promise.all calls to make sure that you have two results that you can use. You can just as well use one Promise.all
router.get('/', (req, res, next) => { //anonymous callback function
let allCategories;
let womenFashion = [],
menFashion = [];
Clothing.groupByBodyPart(function (err, categories) {
if (categories) {
allCategories = categories.slice(0);
const menPromises = [];
const womenPromises = [];
for (let i = 0; i < allCategories.length; i++) { //looping
let category = allCategories[i]._id; //gives a category, eg.,footwear,bottomwear
menPromises.push(
new Promise((resolve, reject) => {
Clothing.getCategoryWise(category, 'M', function (err, products) { //products here will be an array of objects
if (products) {
menFashion[i] = products.slice(0); //storing products into an array for future use
resolve(menFashion[i]);
console.log(menFashion[i]); //accessible here one at a time Eg.,[{slippers},{shoes}]
}
})
})
);
womenPromises.push(
new Promise((resolve, reject) => {
Clothing.getCategoryWise(category, 'F', function (err, products) { //products here will be an array of objects
if (products) {
womenFashion[i] = products.slice(0); //storing products into an array for future use
resolve(womenFashion[i]);
console.log(womenFashion[i]); //accessible here one at a time Eg.,[{slippers},{shoes}]
}
})
})
);
}
Promise.all([Promise.all(menPromises), Promise.all(womenPromises)]).then(([menResults, womenResults]) => {
console.log(menFashion[0]); // not accessible here, so I can't render my page
res.render('index.pug', {
allCategories,
menFashion,
womenFashion
}); //won't work as menFashion and womenFashion aren't available
});
}
});
});

nodejs + Mongodb: Inserting into two collections in sequence repeats last value in second

I am using the following to insert into MongoDB.
var tagData = JSON.parse(data);
var allTags = tagData.tags;
for (var j = 0; j < allTags.length; j++) {
var p = allTags[j].tagId.toString();
for (var k = 0; k < loggerParams.length; k++) {
var q = Object.keys(loggerParams[k]).toString();
if (p === q) {
// Prepare raw data tag
var tagRawDoc = {};
// Simple key-value assignment here
// Document prepared; ready to insert into MongoDB
database.addDocument('tagraw', tagRawDoc, function (err) {
if (err) {
log.info(util.format('Error adding document to tagrawdatas. %s', err.message));
throw err;
} else {
// Prepare history tag
var historyTagDoc = {};
historyTagDoc.tagNameAlias = tagRawDoc.tagNameAlias;
// Simple key-value assignment here
// Document prepared; ready to insert into MongoDB
database.addDocument('taghistory', historyTagDoc, function (err) {
if (err) {
log.info(util.format('Error adding document to tagrawdatas. %s', err.message));
throw err;
}
});
}
});
// Match found; exit loop
break;
}
}
}
The loggerParms is a simple JSON document read from file else-where. It allows for look-up in this code to build the document to be inserted. There will be 12 values in the allTags array. These 12 values are inserted successfully into the tagraw collection. However, in taghistory collection, the values from the last (or most recent) entry made into tagraw collection is repeated 12 times. Why does this happen?
The database.addDocument is shown below. It is a part of this article I am trying to replicate.
var MongoClient = require('mongodb').MongoClient;
var assert = require('assert');
var logger = require('../../util/logger');
var util = require('util');
function DB() {
this.db = "empty";
this.log = logger().getLogger('mongoMange-DB');
}
DB.prototype.connect = function(uri, callback) {
this.log.info(util.format('About to connect to DB'));
if (this.db != "empty") {
callback();
this.log.info('Already connected to database.');
} else {
var _this = this;
MongoClient.connect(uri, function(err, database) {
if (err) {
_this.log.info(util.format('Error connecting to DB: %s', err.message));
callback(err);
} else {
_this.db = database;
_this.log.info(util.format('Connected to database.'));
callback();
}
})
}
}
DB.prototype.close = function(callback) {
log.info('Closing database');
this.db.close();
this.log.info('Closed database');
callback();
}
DB.prototype.addDocument = function(coll, doc, callback) {
var collection = this.db.collection(coll);
var _this = this;
collection.insertOne(doc, function(err, result) {
if (err) {
_this.log.info(util.format('Error inserting document: %s', err.message));
callback(err.message);
} else {
_this.log.info(util.format('Inserted document into %s collection.', coll));
callback();
}
});
};
module.exports = DB;
That's because you are mixing a/multiple synchronous for and asynchronous code with database.addDocument which cause issues with function scope in nodejs.
A simple example of this kind of thing:
for(var i = 0; i < 10; i++){
setTimeout(() => console.log(i), 0);
}
You should use a package like async to handle flow control when iterating arrays/object asynchronously.
Simple example of your code refactored to use async:
var async = require('async');
var tagData = JSON.parse(data);
var allTags = tagData.tags;
async.each(allTags, function(tag, done){
var p = tag.tagId.toString();
var loggerParam = loggerParams.find(function(loggerParam){
var q = Object.keys(loggerParam).toString();
return p === q;
});
var tagRawDoc = {};
// Simple key-value assignment here
// Document prepared; ready to insert into MongoDB
return database.addDocument('tagraw', tagRawDoc, function (err){
if (err) return done(err);
// Prepare history tag
var historyTagDoc = {};
historyTagDoc.tagNameAlias = tagRawDoc.tagNameAlias;
// Simple key-value assignment here
// Document prepared; ready to insert into MongoDB
return database.addDocument('taghistory', historyTagDoc, done);
});
}, (err) => {
if(err) throw err;
console.log('All done');
});

Saving MongoJS select result to an array in NodeJS and then modifying it

Having a hard time saving and modifying the result of a MongoJS query in NodeJS.
router.post('/getMySubjects', function (req, res) {
var data = [];
if (req.body.type == 'Professor') {
db.subjects.find({ contractorID: req.body.userId }, function (err, subjects) {
data = subjects; // SUBJECTS ARE NOW SAVED TO DATA SUCCESSFULLY
data.forEach(function(subject) {
db.faculties.find({ _id: mongojs.ObjectID(subject.subjectFor_faculty)}, function (err, faculty) {
subject.faculty = faculty; // BUT HERE I WANT TO ADD A FACULTY (object)
// BASED ON THE subjectFor_faculty (id)
// WHICH IS LOCATED IN EVERY (subject)
// ELEMENT IN DATA ARRAY
});
});
res.send(data); // THE DATA HERE IS UNMODIFIED
// SAME AS DATA ON LINE 6
});
}
});
I presume that I don't yet fully understand how the response works (btw the app is made with express framework), because when the data is first saved on line 6, the next step is sending the data, and only THEN the app goes goes into the forEach loop...
You are making async mongo queries. you have to make them work sync for getting right data. here's implementation using promises.
router.post('/getMySubjects', function (req, res) {
var data = [];
if (req.body.type == 'Professor') {
db.subjects.find({ contractorID: req.body.userId }, function (err, subjects) {
data = subjects;
var promises = [];
data.forEach(function(subject) {
var promise = new Promise(function(resolve, reject) {
db.faculties.find({ _id: mongojs.ObjectID(subject.subjectFor_faculty)}, function (err, faculty) {
resolve(faculty);
});
});
promises.push(promise);
});
Promise.all(promises).then(function(values){
for(var i = 0; i< values.length;i++){
data[i].faculty = values[i];
}
res.send(data);
});
});
}
});

Resources