Cannot return data fetched from mongodb - javascript

I can successfully fetch all data stored in a collection called "topics" by using find() with cursor and foreach.
However, when I try to set the fetched information to variable call "data" and send it back to the page, it always display an empty array.
How can I fix it ?
app.get('/api/get_topics', (req, res)=>{
let data = [];
MongoClient.connect(url, (err, db)=>{
const dbo = db.db('adoption');
let cursor = dbo.collection('topics').find();
cursor.forEach((el)=>{
const obj = {
img : el.img,
title : el.title,
content : el.content
}
data.push(obj);
});
db.close();
});
res.json(data); // [] ---> always empty
res.end();
});

try this
app.get('/api/get_topics', (req, res)=>{
let data = [];
MongoClient.connect(url, (err, db)=>{
const dbo = db.db('adoption');
let cursor = dbo.collection('topics').find();
cursor.forEach((el)=>{
const obj = {
img : el.img,
title : el.title,
content : el.content
}
data.push(obj);
});
db.close();
res.json(data); // [] ---> always empty
res.end();
});
});

You need to return the data after your connection to the database is done.
Before you close the connection and after your forEach: because "forEach" is syncron & blocks the event loop
app.get('/api/get_topics', (req, res)=>{
let data = [];
MongoClient.connect(url, (err, db)=>{
const dbo = db.db('adoption');
let cursor = dbo.collection('topics').find();
cursor.forEach((el)=>{
const obj = {
img : el.img,
title : el.title,
content : el.content
}
data.push(obj);
});
res.json(data);
res.end();
db.close();
});
});
Note: You should create a global connection and not for every request.
A connection can be re used.

MongoClient.connect(url, callback) ...
callback (function) – this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the initialized db object or null if an error occured. (https://mongodb.github.io/node-mongodb-native/api-generated/mongoclient.html)
Which hints that the connect() fnc is an async function. Hence res.json(data); res.end(); will be executed before the callback of the connect function is being called where you populate your array.

The reason for the empty data is because MongoClient.connect() is an asynchronous function, meaning the code is not waiting for your callback to push the content to your data array before setting the response data.
As others have pointed out, you can set and end your response within your callback, but I am skeptical that that will work. Another approach would be to use an async/await method. You can see this usage from the official MongoDB docs.
In addition, I believe you can omit res.end() or move it to the catch.
For example:
app.get('/api/get_topics', (async (req, res) => {
try {
let data = [];
const db = await MongoClient.connect(url);
const dbo = db.db('adoption');
const cursor = dbo.collection('topics').find();
cursor.forEach((el)=>{
const obj = {
img : el.img,
title : el.title,
content : el.content
}
data.push(obj);
});
db.close();
res.json(data);
} catch (err) {
// Handle an error here...
console.log(err);
res.end();
}
}));

Related

How to wait for a variable to be populated by an api request before passing it to a webpage as an argument?

I'm new to JavaScript and cannot seem to make this work , the topic of quiz depends on the user input... when the user presses next , I get the topic (this also takes user to the main quiz page), then i have to fetch data from the api with the topic as a parameter... I have to process the result of the fetch operation.. Then I have to pass that info to to the main quiz page... but the variable that is supposed to be populated by the fetch request is still undefined when i pass is to the main quiz page
var Allquestions;
var sheetdb = require('sheetdb-node');
// create a config file
var config = {
address: 'https://sheetdb.io/api/v1/9djmf8ydc7hwy',
};
//sheetdb
// Create new client
var client = sheetdb(config);
function downloadquestions(topic) {
console.log(topic);
client.read({ limit: 2, sheet: topic }).then(function(data) {
console.log(data + " in client.read func")
processQuestions(data);
}, function(err){
console.log(err);
});
}
async function processQuestions(data) {
console.log(data + "data in process");
Allquestions = JSON.parse(data);
console.log(Allquestions[0].Question + " This is defined");
}
app.get("/", (req, res) => {
res.render("pages/index", { title: "Home"});
});
// app.post("/" , urlencodedParser ,(req , res) => {
// console.log(req.body.topic);
// })
app.get("/questions", urlencodedParser , (req , res) => {
downloadquestions(req.body.topic);
console.log(Allquestions + " this is undefined");
res.render("/pages/quizpage" , {Allquestions})
})
There are a few issues with your code, you have a broken promise chain, client.read( is a promise, and that promise is going nowhere. You either return it, or await it. To be able to await your will need to also mark your route (req, res) as async too.
Your code is a little mixed up, you have Allquestions as a global var, this isn't great for multi-user, as the last topic is going to override this each time.
Also try and avoid swallowing exceptions in utility functions, try and keep your exception handling at the top level, eg. in your case inside your req/res handler.
So with all this in mind, your refactored code could look something like ->
const sheetdb = require('sheetdb-node');
// create a config file
const config = {
address: 'https://sheetdb.io/api/v1/9djmf8ydc7hwy',
};
//sheetdb
// Create new client
const client = sheetdb(config);
async function downloadquestions(topic) {
const data = await client.read({ limit: 2, sheet: topic });
return processQuestions(data);
}
function processQuestions(data) {
return JSON.parse(data);
}
app.get("/", (req, res) => {
res.render("pages/index", { title: "Home"});
});
app.get("/questions", urlencodedParser , async (req , res) => {
try {
const allQuestions = await downloadquestions(req.body.topic);
res.render("/pages/quizpage" , {Allquestions});
} catch (e) {
console.error(e);
res.end('There was an error');
}
})

Middleware authentification with a condition not working Express. Node.js

I am currently developing a website with an API that I built with node.js, express and MongoDb for the database.
I am curretly learning node and express and cant find my way to create a middleware to verify that the USER ID matches the POSTED BY ID from a COMMENT. That way the USER can only delete his own comments.
My middleware looks like this
verifyUserIdPostedBy.js
const Comment = require('../models/Comment');
var userId
var postedById
module.exports = {
verfyUserIdPostedBy: function (req, res, next) {
userId = req.header('userId')
postedById = Comment.findOne({ _id: req.params.commentId}).populate('postedBy') .exec( function (error, body) {
if (error) throw new Error(error);
req.postedById = body.postedBy._id // assign the ID to the req object
console.log(req.postedById);
});
console.log(userId);
if(userId !== req.postedById)
return res.status(500).json({message: 'Stopped'})
return next();
},
}
My console.log() in the middleware show me exactly the 2 values that I want to compare but I get the error 'Stopped' and my verification never happens. I tried accesing the route with the comment owner and also with not the comment owner and none works
and my route looks like this
comments.js
const express = require('express');
const router = express.Router();
const Comment = require('../models/Comment');
const verify = require('./verifyToken');
const {verfyUserIdPostedBy} = require('./verfyUserIdPostedBy')
// DELETE COMMENT
router.delete('/:commentId', verify, verfyUserIdPostedBy, async (req, res) => {
try {
const removedComment = await Comment.deleteOne({ _id: req.params.commentId });
res.json(removedComment);
} catch(err){
res.json({message:err});
}
})
Like I said I am new at this but cant find a way to do it properly.
Appretiate in advance any help and advice.
Mario
I add comments in your code to explain how it works :
verfyUserIdPostedBy: function (req, res, next) {
userId = req.header('userId')
postedById = Comment.findOne({ _id: req.params.commentId}).populate('postedBy') .exec( function (error, body) {
/* -----this is a callback function------ */
/* the code inside the callback function is executed only when findOne finish and **after** the code outside */
if (error) throw new Error(error);
req.postedById = body.postedBy._id // assign the ID to the req object
console.log(req.postedById);
});
/* this code is executed before the code inside the callback function */
console.log(req.postedById); // undefined, you can check
console.log(userId);
if(userId !== req.postedById) // this is always true
return res.status(500).json({message: 'Stopped'}) // and this line always executes
return next(); // and this line never execute
},
The concept is callback. I strongly advise you to research this keyword, callback is used massively in NodeJS. Nowadays, there are Promise and async/await that allow developers to write asynchronous code in a "synchronous way", but callback is the base.
In order for your code works, 1 simple solution (there are many solutions!) is put comparison code into the callback block, something like :
const Comment = require('../models/Comment');
var userId
var postedById
module.exports = {
verfyUserIdPostedBy: function (req, res, next) {
userId = req.header('userId')
postedById = Comment.findOne({ _id: req.params.commentId}).populate('postedBy') .exec( function (error, body) {
if (error) throw new Error(error);
req.postedById = body.postedBy._id // assign the ID to the req object
console.log(req.postedById);
console.log(userId);
if(userId !== req.postedById)
return res.status(500).json({message: 'Stopped'})
return next();
});
},
}

Access Output from MySQL in another file

I want to access the variable "result" from the function which contains the query.
When I want to access it from another file, in which I am trying to work with the output after a POST Request, the variable is declared as "undefined".
This is the file in which i execute the query:
const db = require('../db/connect');
module.exports = {
getID(name){
db.query(`SELECT CWID FROM user WHERE surname = '${name}'`, function(error, result, fields){
if(error) console.log(error);
console.log(result);
});
}
}
And this is the file where I want to work with the data:
router.post('/test', function(req, res){
const data = queries.getID(req.body.name);
console.log(data);
res.render('new test', {title: "test"});
})
Can anybody help me with this?
Here's an example of querying using mysql and async/await. This should do what you would like:
Query file
const db = require('./db/connect');
module.exports = {
getID(name) {
return new Promise((resolve, reject) => {
db.query(`SELECT CWID FROM user WHERE surname = '${name}'`, function(error, result, fields) {
if (error) {
reject(error);
} else {
resolve(result);
}
});
});
}
}
Main File
router.post('/test', async function(req, res){
const data = await queries.getID(req.body.name);
console.log("Query result: ", data);
res.render('new test', {title: "test"});
});
The reason your result is undefined in your initial example is that you're using asynchronous i/o (normal in Node.js). By returning a Promise from getID, we can make async. calls easily and with some nice code syntax.

NodeJs is returning first document only

I am using NodeJs and MongoDb as a back-end service.In my collection i have several documents having fields named _id and Name but Node is returning only first document and showing error in console.I want to fetch only Name field of a document.
Error:
Here is my code:
var express = require('express');
var MongoClient = require('mongodb').MongoClient;
var url = "mongodb://localhost:27017/";
var bodyParser = require('body-parser');
var app = express();
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({extended:true}));
app.post('/offers',(req, res) => {
MongoClient.connect(url, (err, db) => {
if(err) throw err;
var dbo = db.db('Tiffino_db');
dbo.collection("Offers")
.find({},{ projection: { _id: 0 } })
.toArray((err, result) => {
if (err) {
console.log("Error:", +err);
}
else {
for(var i = 0;i<result.length;i++){
res.send(result[i].Name);
}
db.close();
}
});
});
});
Please let me know what I did wrong in above code.
THANKS
It looks like you're trying to send multiple responses in a loop using Response.Send() as a response is only sent once.
This won't work, you'll need to create an array of names, and then use Response.Send() to do this once.
Using res.send(result.map(r => r.name)) would probably work
You should aggregate the results into a variable and then call res.send() only once:
let resultString;
for(var i = 0;i<result.length;i++){
resultString += result[i].Name + '\n';
}
res.send(resultString);
If you want to stream the results, you can use res.write() and when done call res.end()
You can not use res.send() multiple time in a for loop, try somthing like this
var data=[];
for(var i = 0;i<result.length;i++){
data.push(result[i].Name);
}
res.send(data);
app.post('/offers',(req, res) => {
MongoClient.connect(url, (err, db) => {
if(err) throw err;
var dbo = db.db('Tiffino_db');
dbo.collection("Offers")
.find({},{ projection: { _id: 0 } })
.toArray((err, results) => {
if (err) {
console.log("Error:", +err);
}
else {
response = results.map(result => result.Name);
//res.send(response);
// in json format
res.json(response);
db.close();
}
});
});
});
...
...
else {
output = result.map(offer => offer.name);
res.send({output});
db.close();
}
This should work as you can only send response once on a single response object and you are calling that for results.length times.
I'm pretty sure you're not supposed to be calling res.send(...) multiple times. Since you're calling it inside a loop, it will send the first document and fail in the next as expected.

NodeJs app "hanging" or "freezing" whenever an error occurs

When I make multiple post requests to my nodejs server and all of the parameters are correct, everything works fine (and doesnt freeze) but when I make multiple post requests with incorrect parameters that gives an error, my nodejs server just freezes/hangs for a few minutes. Why is this?
Here is my code btw
app.post('/pushtransaction', function(req, res) {
console.log(req.body);
console.log(5);
if (req.body.sigs) {
let sigver = xmf.modules.ecc.Signature.from(req.body.sigs).toString();
let lasig = [sigver];
console.log(req.body.packedTr);
let transi = JSON.parse(req.body.packedTr);
//let sigver = req.body.sigs;
let package = {
compression: 'none',
transaction: transi,
signatures: lasig
}
console.log(package);
//Pushes tx in correct format
xmf.pushTransaction(package).then(result=>{
res.send(result);
res.end();
console.log(result);
}).catch(err => {
console.log(err)
});
}
})
When your error is encountered, your Node server does not know what to do other than console.log() the error. It needs to end that request and send some response. You can res.status(400).send({ error: err }) when you're within the catch.
Make sure res.send() method gets called every time in your request.
Updated Javascript:
app.post('/pushtransaction', function(req, res) {
console.log(req.body);
console.log(5);
if (req.body.sigs) {
let sigver = xmf.modules.ecc.Signature.from(req.body.sigs).toString();
let lasig = [sigver];
console.log(req.body.packedTr);
let transi = JSON.parse(req.body.packedTr);
//let sigver = req.body.sigs;
let package = {
compression: 'none',
transaction: transi,
signatures: lasig
}
console.log(package);
//Pushes tx in correct format
xmf.pushTransaction(package).then(result=>{
res.send(result);
res.end();
console.log(result);
}).catch(err => {
console.log(err);
res.status(400).send();
});
}
res.status(400).send();
})
Additionally you don't have to call res.end() if you call res.send(). see Must res.end() be called in express with node.js?
Adding to other answers, you can add a middleware for timeouts, if any service fails to respond in some time, like
var timeout = require('connect-timeout');
app.use(timeout('5s'));

Categories

Resources