I have a problem returning some data from a db in mongodb. I put you in situation.
I have a file called db.js, which has the following content:
const mongoose = require('mongoose');
var libro = mongoose.Schema({
titulo: String,
estado: String,
autor: String,
genero: String
});
module.exports = mongoose.model('estanteria', libro);
I have another file called estanteria.js that has the following content:
const Libreria = require('./db');
const mongoose = require('mongoose');
mongoose.connect('mongodb://localhost/libreria', (err) => {
if(err) throw err;
console.log("Conexión a db correcta");
});
...
function allBooks(){
var libros = Libreria.find({})
return libros;
}
exports.allBooks = allBooks;
The problem I have in the function allBooks(), I do not know how to return the contents of the collection in an array of objects, and then display it on the web. Can somebody help me?
Inside allBooks function add a callback function to return after find operation.
function allBooks(){
Libreria.find({}).exec(function(error, records) {
if (!error) {
res.send({
success : true,
records : records
});
} else {
console.log(error);
res.send({
success : false,
error : error
});
}
});
}
exports.allBooks = allBooks;
Use JSON.stringify() to encode libros in a jhson format then write it as an response to the request ( The request recieved by the server )
Libreria.find({}) is an async operation, you need to use Promises way to handle this. As shown below:
Libreria.find returns a promise and you can handle the resolve state of this promise in .then method and if any error occurs it will be done in .catch
function allBooks(){
Libreria.find({})
.then(function(books) {
return books;
})
.catch(function(error){
return error;
})
}
// An exmaple router handler as follow:
router.get("/posts", function(req, res){
allBooks()
.then(function(records){
res.json({status: true, records})
})
.catch(function(error){
res.json({status: false, error })
});
})
Read more about mongoose promises: http://mongoosejs.com/docs/promises.html
Promises in general: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise
Related
In my app I have a category collection that saves the category title with its image ID. The image is saved in another collection with its meta data like path , type and so on. So for retrieving category I should retrieve category image in image collection by its ID and add image path to category object that is retrieved from category collection and send it to client...But I don't know where should I send categories to client?
When I send the response face this error :
throw er; // Unhandled 'error' event
^
Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
at ServerResponse.setHeader (_http_outgoing.js:561:11)
at ServerResponse.header (H:\node.js\online-store\app\node_modules\express\lib\response.js:771:10)
at ServerResponse.send (H:\node.js\online-store\app\node_modules\express\lib\response.js:170:12)
at ServerResponse.json (H:\node.js\online-store\app\node_modules\express\lib\response.js:267:15)
at ServerResponse.send (H:\node.js\online-store\app\node_modules\express\lib\response.js:158:21)
at H:\node.js\online-store\app\controllers\pcategory.controller.js:123:19
at H:\node.js\online-store\app\node_modules\mongoose\lib\model.js:4845:18
at processTicksAndRejections (internal/process/task_queues.js:77:11)
Emitted 'error' event on Function instance at:
at H:\node.js\online-store\app\node_modules\mongoose\lib\model.js:4847:15
at processTicksAndRejections (internal/process/task_queues.js:77:11) {
code: 'ERR_HTTP_HEADERS_SENT'
}
This is my code :
exports.getAll = async (req, res) => {
try{
const categories = await ProductCategory.find({});
categories.map(async(category)=>{
await File.findById(category.imageID).exec(function(err,file){
if(err){
console.log(err)
}else if(file) {
category.imagePath = file.file_path;
tempCategories.push(category)
}
res.send(tempCategories);
})
})
return res.send(tempCategories);
}catch {
res.json(err =>{
console.log(err);
res.status(500).send({
message:
err.message || "There is an error in retrieving category"
});
})
}
}
The problem is that nothing in your code is waiting for the asynchronous operations you're doing in your map callback to complete, so it does the res.send at the end right away — and then does res.send again within the map callback later when the async operations complete. Instead, wait for them to finish and send the result.
Also, you're using res.send where I suspect you want res.json, and using res.json later incorrectly (it doesn't take a callback).
See comments:
exports.getAll = async (req, res) => {
try {
// Get the categories
const categories = await ProductCategory.find({});
// Get the files for the categories, wait for the result
const result = await Promise.all(categories.map(async (category) => {
const file = await File.findById(category.imageID).exec();
// You probably can't modify the `category` object, so let's create
// and return a new object
return {...category, imagePath: file.file_path};
}));
// Send the result converted to JSON
return res.json(tempCategories);
} catch (err) { // Accept the error
// Send an error response
res.status(500).json({
message: err.message || "There is an error in retrieving category"
});
}
};
Side note: Your original code was using map without using the array it creates. That's an antipattern (sadly it seems to be one someone somewhere is teaching). I wrote up why and what to do instead here. (In my update to your code, I still use map, but I use the array it creates, passing it to Promise.all so we can wait for all those promises to settle.)
Your Code Like this,
Now Issue is You are sending two times Headers.
You can use like this, Firstly Declare array and push into it what you need and then last of your logic return it or send it.
exports.getAll = async (req, res) => {
try {
const categories = await ProductCategory.find({});
let tempCategories = []; // New Line
await Promise.all(categories.map(async (category) => {
await File.findById(category.imageID).exec(function (err, file) {
if (err) {
console.log(err)
} else if (file) {
category.imagePath = file.file_path;
tempCategories.push(category)
}
});
return category;
}));
res.send(tempCategories);
} catch {
res.json(err => {
console.log(err);
res.status(500).send({
message:
err.message || "There is an error in retrieving category"
});
})
}
}
I am creating a document in my MongoDB database using Mongoose with the following code:
workoutsRouter.post('/', async (req, res) => {
await mongoose.connect('mongodb+srv://nalanart:<password>#cluster0.2iplh.mongodb.net/workout-app-db?retryWrites=true&w=majority',
{ useNewUrlParser: true, useUnifiedTopology: true })
await Workout.create({
mains: req.body.mains,
accessories: req.body.accessories
}, err => {
if(err) {
throw err
} else {
res.sendStatus(201)
}
})
})
My problem is that it does not send a 201 status as I have written it to. Any idea why? It just says Sending... on Postman
And here it appears in my collection:
Yeah, because you are both awaiting and passing a callback. You are mixing the await and callback syntaxes. If you pass Mongoose a callback, it won't return a Promise, so it will be awaited forever, since it will never resolve. Either await it, or pass it a callback, not both. Also, try res.status(201).end()
try {
await Workout.create({
mains: req.body.mains,
accessories: req.body.accessories
});
res.status(201).end();
} catch (err) {
throw err
}
I've got an API endpoint where user can specify id of the collection he wants to delete. Router read a json file, iterate through collections and delete found record:
router.delete('/todos/:id', (req,res)=>{
const id = req.params.id
fs.readFile('todos.json', 'utf8', (err,data) =>{
if (err) throw err
const JSONdata = JSON.parse(data)
JSONdata.todos.forEach((todo, index)=>{
if(todo.id == id){
JSONdata.todos.splice(index,1)
fs.writeFile('todos.json',JSON.stringify(JSONdata), 'utf8', (err) =>{
if (err) throw err
return res.json({"deleted" : true})
})
}
})
})
res.status(404).json({errors: ['task not found']})
})
However instead of proper response {"deleted": true} I get this error:
Server is running on port 3000
_http_outgoing.js:470
throw new ERR_HTTP_HEADERS_SENT('set');
^
Error [ERR_HTTP_HEADER
I know this is related to the asynchronous nature of the node. But I was sure that adding return keyword if loop found a specific record will make it work. How can I fix it?
Jest a litle change:
router.delete('/todos/:id', (req,res)=>{
const id = req.params.id
fs.readFile('todos.json', 'utf8', (err,data) =>{
if (err) throw err
const JSONdata = JSON.parse(data)
JSONdata.todos.forEach((todo, index)=>{
if(todo.id == id){
JSONdata.todos.splice(index,1)
fs.writeFile('todos.json',JSON.stringify(JSONdata), 'utf8', (err) =>{
if (err) throw err
return res.json({"deleted" : true})
})
}
})
return res.status(404).json({errors: ['task not found']})
})
})
fs.readFile is an Async function, it means that in your code, the
res.status(404).json({errors: ['task not found']})
will be run first, and then the callback inside the fs.readFile function.
If you want to use a synchronic function, use fs.readFileSync.
You can read more about it here https://nodejs.org/api/fs.html#fs_fs_readfile_path_options_callback
Code snippet below is using sails waterline ORM to make DB queries and sending response. However, the execution flow is weird, The code outside of map function is running before the map function has finished executing. 'I am outside of map' is being printed in the console before 'I am inside of map'. I think it can be solved this using Promise or async / await. I have tried using Promise.all() below, but it doesn't work, the response is always an empty array. I would be grateful if you could give an example on how to solve this kind of issues.
allMembers: (req, res) => {
const projectId = req.params.id;
ProjectMembers.find({projectId: projectId}).exec( (err, members) => {
if(err) res.serverError("bad request!");
if(members.length === 0) res.notFound({message: "No members are found for this project!"});
let membersInfo = [];
let promise = Promise.all(members.map(m => {
User.findOne({id: m.userId}).exec( (err, user) => {
if(err) membersInfo.push({name: null, userId: null, email:null,rate:null, error: 'Internal error!'})
else if(!user) membersInfo.push({name: null, userId: null, email:null,rate:null, error: 'No user found'})
else membersInfo.push({name: user.name, userId: user.id, rate: m.rate, error: null})
console.log("i am inside of map");
})
}));
console.log("I am outsie of map")
promise.then( (resolve) => {return res.ok({members: membersInfo})});
}
I was about to tell you "don't use queries in .map", but on looking, I think your code is quite close to working. The argument of Promise.all has to be an array of promises. Each User.findOne is indeed a promise - the stumbling block is that once you use .exec it no longer returns a promise.
I think the answer is to do your processing inside the .then instead of right inside the .map:
ProjectMembers.find({projectId: projectId}).exec( (err, members) => {
if(err) return res.serverError("bad request!");
if(members.length === 0) return res.notFound({message: "No members are found for this project!"});
let promise = Promise.all(members.map(m => User.findOne({id: m.userId})));
promise.then( (values) => {
// values is the array of user objects returned from the array of queries
let membersInfo = values.map((user) => {
if (!user) {
return {name: null, userId: null, email:null,rate:null, error: 'No user found'};
} else {
return {name: user.name, userId: user.id, rate: m.rate, error: null};
}
});
return res.ok({members: membersInfo});
}, (err) => {
return res.serverError("Error finding users");
});
The promise only has a single fail callback, so you lose the ability to individually catch and handle querying errors (though you can still individually handle not-found results).
I'm trying to do something like this
function retrieveUser(uname) {
var user = User.find({uname: uname}, function(err, users) {
if(err)
console.log(err);
return null;
else
return users[0];
return user;
But this returns a document instead of a user object. The parameter users is an array of user objects matching the query, so how would I store one of the objects into a variable that my function could return?
The function User.find() is an asynchronous function, so you can't use a return value to get a resultant value. Instead, use a callback:
function retrieveUser(uname, callback) {
User.find({uname: uname}, function(err, users) {
if (err) {
callback(err, null);
} else {
callback(null, users[0]);
}
});
};
The function would then be used like this:
retrieveUser(uname, function(err, user) {
if (err) {
console.log(err);
}
// do something with user
});
Updated on 25th Sept. 2019
Promise chaining can also be used for better readability:
Model
.findOne({})
.exec()
.then((result) => {
// ... rest of the code
return Model2.findOne({}).exec();
})
.then((resultOfModel2FindOne) => {
// ... rest of the code
})
.catch((error) => {
// ... error handling
});
I was looking for an answer to the same question.
Hopefully, MongooseJS has released v5.1.4 as of now.
Model.find({property: value}).exec() returns a promise.
it will resolve to an object if you use it in the following manner:
const findObject = (value) => {
return Model.find({property: value}).exec();
}
mainFunction = async => {
const object = await findObject(value);
console.log(object); // or anything else as per your wish
}
Basically, MongoDB and NodeJS have asynchronous functions so we have to make it to synchronous functions then after it will work properly as expected.
router.get('/', async function(req, res, next) {
var users = new mdl_users();
var userData = []; // Created Empty Array
await mdl_users.find({}, function(err, data) {
data.forEach(function(value) {
userData.push(value);
});
});
res.send(userData);
});
In Example, mdl_users is mongoose model and I have a user collection(table) for user's data in MongoDB database and that data storing on "userData" variable to display it.In this find function i have split all documents(rows of table) by function if you want just all record then use direct find() function as following code.
router.get('/', async function(req, res, next) {
var users = new mdl_users();
var userData = await mdl_users.find();
res.send(userData);
});