Related
im trying to update an oject in a sub-array and instead of replacing and updating the data. it adds a new enetry.
controller.js:
const updateSubCategory = asyncHandler(async (req, res) => {
const {
dataArray
} = req.body
const categories = await Category.find({})
if (categories) {
await Category.updateOne({
"SubCats._id": req.params.id
}, {
"$set": {
SubCats: {
name: dataArray[0],
image: dataArray[1]
}
}
}, {
"multi": true
})
res.json({
message: 'sub-category updated'
})
} else {
res.status(404)
throw new Error('Error')
}
})
I think you need this, but i am not sure, if you dont need this, if you can give sample data and expected output in json.
You can try an example PlayMongo
It updates the fields inside not replace all the embeded document (your query does that).
const updateSubCategory = asyncHandler(async (req, res) => {
const {
dataArray
} = req.body
const categories = await Category.find({})
if (categories) {
await Category.updateOne({
"SubCats._id": req.params.id
}, {
"$set": {
"SubCats.name" : dataArray[0],
"SubCats.image" : dataArray[1]
}
}
}, {
"multi": true
})
res.json({
message: 'sub-category updated'
})
} else {
res.status(404)
throw new Error('Error')
}
})
I get all data successfully in my node.js code using Profile.find(),but when I want to add filter in api, it get 500 error.
Profile.js
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
//Create Schema
const ProfileSchema = new Schema({
type: {
type: String,
},
description: {
type: String,
},
cash:{
type: String,
required: true,
}
})
module.exports = Profile = mongoose.model("profile",ProfileSchema);
api.js
const router = express.Router();
const Profile = require('../../models/Profile');
router.get("/",passport.authenticate('jwt',{session:false}),(req, res) => {
Profile.find().then(profiles => {
res.json(profiles)
})//works
})
router.get("/",passport.authenticate('jwt',{session:false}),(req, res) => {
var condition = {'cash' : '5000'};
Profile.find(condition,function(err,res){//error for this
res.json(res)
})
})
I am new to mongoose and do not sure how to do the filter and pagination for my code.
Appreciate for any kind help.
Update
Thanks to #Amila Senadheera, I finally find the solution to do filter and pagination using below code:
var condition = { cash: '5000' };
Profile.find(condition).skip(1).limit(10).then(profiles => {
res.json(profiles)
})
To use aggregation, it works like below:
router.post(
"/",
passport.authenticate("jwt", { session: false }),
async (req, res) => {
// page index
let pageIndex = 1;
// profiles per page
let pageSize = 10;
if (req.body.pageIndex !== null || req.body.pageIndex !== "undefined") {
pageIndex = req.body.pageIndex;
}
if (req.body.pageSize !== null || req.body.pageSize !== "undefined") {
pageSize = req.body.pageSize;
}
let condition = { description: "1" };
try {
const getAllProfile = await Profile.aggregate([{
$facet: {
data:[
{ $match: condition },
{ $skip: (pageIndex - 1) * pageSize },
{ $limit: pageSize },
],
totalCount:[
{ $match: condition },
{ $count: 'totalCount' }
]
}
}]) ;
res.json(getAllProfile);
} catch (error) {
res.status(404).json(error);
}
}
);
The find function can only accept query and projection as arguments. You are not allowed to give a callback as an argument. then should be chained to the Promise returned by find.
Try like this
var condition = {'cash' : '5000'};
Profile.find(condition).then(profiles => {
res.json(profiles)
});
Profiles with pagination.
you can use the MongoDB aggregation framework for that (which will make it possible to get additional information like the total entries and count of the entries on the current page (the last page might have a lesser number than the page limit)). You need to send start and limit as the request body.
router.get(
"/",
passport.authenticate("jwt", { session: false }),
async (req, res) => {
// page index
let start = 0;
// profiles per page
let limit = 10;
if (req.body.start !== null || req.body.start !== "undefined") {
start = req.body.start;
}
if (req.body.limit !== null || req.body.limit !== "undefined") {
limit = req.body.limit;
}
let condition = { cash: "5000" };
try {
const aggregatedData = await Profile.aggregate([
{
$facet: {
data: [
{ $match: condition },
{
$project: {
type: 1,
description: 1,
cash: 1
}
},
{ $skip: start * limit },
{ $limit: limit }
],
metaData: [
{
$group: {
_id: null,
count: { $sum: 1 }
}
}
]
}
}
]);
return res.status(200).json({
data: aggregatedData[0].data,
metaData: {
total: aggregatedData[0].metaData[0].count,
start: start
}
});
} catch (error) {
return res.status(500).json({
error: error
});
}
}
);
I have one question about a problem that I'm not able to fix. I try to update push a string passed via Query in my mongoose collection.
My collection are like this:
{
"_id": {
"$oid": "6199288597e42bf84d017f9e"
},
"name": "Lisa",
"surname": "Bianchi",
"ID_school": "afbH598U3",
"classes": [
{
"class": "5A",
"activities": {
"in_progress": [],
"finisched": []
},
"_id": {
"$oid": "6199288597e42bf84d017f9f"
}
},
{
"class": "1A",
"activities": {
"in_progress": [],
"finisched": []
},
"_id": {
"$oid": "6199288597e42bf84d017fa0"
}
}
],
"email": "insegnante#a.com",
"__v": 0
}
and I try to push a string in in_progress array that match, for example, with class:"5A" using this way:
import db from "../models/index.js";
const Teacher = db.teacher
const updateActivity = (req, res) => {
const query = { _id: req.query.id};
const update = {$push:{'classes.$[group].activities.in_progress': req.query.data } };
const options = {arrayFilters: { 'group.class': req.query.class }};
Teacher.findOneAndUpdate(query, update, options).exec((err, data) => {
if (err) {
res.status(400).send({ message: err });
return;
} else {
res.status(200).send(data);
}
})
}
const API = {
updateActivity
}
export default API
The query works fine, but nothing was pushed. I tested whit Insomnia passing in the Query field
id = 6199288597e42bf84d017f9e;
class:'5A';
data:"pushed"
Any suggestion? Thanks!
try this way by passing classes.class in query and also change push to $push:{'classes.$.activities.in_progress': req.query.data }
const updateActivity = (req, res) => {
const query = { _id: req.query.id ,'classes.class': req.query.class};
const update = {$push:{'classes.$.activities.in_progress': req.query.data } };
Teacher.updateOne(query,update).exec((err, data) => {
if (err) {
res.status(400).send({ message: err });
return;
} else {
res.status(200).send(data);
}
})
}
There are two ways of doing this:
Option 1: arrayFilters - more flexible Docu
The option you are using.
You have a syntax error - arrayFilters should be an array of documents.
const updateActivity = (req, res) => {
const query = { _id: req.query.id };
const update = {
$push:{ 'classes.$[group].activities.in_progress': req.query.data }
};
// This MUST be an array of filter documents!
const options = { arrayFilters: [{ 'group.class': req.query.class }] };
Teacher
.findOneAndUpdate(query, update, options)
.exec((err, data) => {
if (err) {
res.status(400).send({ message: err });
return;
} else {
res.status(200).send(data);
}
});
}
Option 2: Via Query (as answered by #Saurabh Mistry)
Repeating his answer for completeness
By specifying a query that targets a particular element in an array within result documents.
const updateActivity = (req, res) => {
const query = {
_id: req.query.id,
'classes.class': req.query.data,
};
const update = {
$push:{ 'classes.$.activities.in_progress': req.query.data }
};
Teacher
.findOneAndUpdate(query, update, options)
.exec((err, data) => {
if (err) {
res.status(400).send({ message: err });
return;
} else {
res.status(200).send(data);
}
});
}
This question already has answers here:
Why is my variable unaltered after I modify it inside of a function? - Asynchronous code reference
(7 answers)
Closed 4 years ago.
I wrote a controller and I do not understand why in the method then my array is correct and I need to send it, and for .then () my array is empty. I can not send a res in the middle of the loop.
exports.getRecipientdata = (req, res) => {
const userId = req.params.recipientId;
const sendersArray = [];
Transaction.findAll({
where: {
id_recipient: userId,
},
}).then(transactions => {
for (let i = 0; i < transactions.length; i++) {
User.findOne({
where: {
id: transactions[i].id_sender,
},
attributes: ['id', 'name', 'surname'],
include: [
{
model: Transaction,
where: { id_sender: db.Sequelize.col('user.id') },
attributes: [
'amount_money',
'date_time',
'transfer_title',
'id_recipient',
'id_sender',
],
},
],
})
.then(sender => {
sendersArray.push(sender);
console.log(JSON.stringify(sendersArray)); // ok
})
.catch(err => {
console.log(err);
});
}
console.log('sendersArray', sendersArray); // empty?
res.send(sendersArray);
});
};
The for loop is sending out requests which asynchronously populate sendersArray. If you console.log(sendersArray) synchronously after the for loop has run, it won't have been populated yet. Instead of a for loop, use .map and Promise.all to wait for all requests to complete:
exports.getRecipientdata = (req, res) => {
const userId = req.params.recipientId;
const sendersArray = [];
Transaction.findAll({
where: {
id_recipient: userId,
},
}).then(transactions => {
return Promise.all(transactions.map(({ id_sender }) => (
User.findOne({
where: {
id: id_sender,
},
attributes: ['id', 'name', 'surname'],
include: [
{
model: Transaction,
where: { id_sender: db.Sequelize.col('user.id') },
attributes: [
'amount_money',
'date_time',
'transfer_title',
'id_recipient',
'id_sender',
],
},
],
})
.then(sender => {
sendersArray.push(sender);
})
.catch(err => {
console.log(err);
})
)));
})
.then(() => {
res.send(sendersArray);
});
};
Another possibility, rather than pushing to an outer variable, would be to use the array created by Promise.all, and filter by boolean to remove the falsey values (since the catch's lack of a return value will have resulted in undefineds being present in the result of the Promise.all array):
exports.getRecipientdata = (req, res) => {
const userId = req.params.recipientId;
Transaction.findAll({
where: {
id_recipient: userId,
},
}).then(transactions => {
return Promise.all(transactions.map(({ id_sender }) => (
User.findOne({
where: {
id: id_sender,
},
attributes: ['id', 'name', 'surname'],
include: [
{
model: Transaction,
where: { id_sender: db.Sequelize.col('user.id') },
attributes: [
'amount_money',
'date_time',
'transfer_title',
'id_recipient',
'id_sender',
],
},
],
})
.catch(err => {
console.log(err);
})
)));
})
.then((sendersArray) => {
res.send(sendersArray.filter(Boolean));
});
};
Getting messy with promises and async & await. Would like to get your advice, what is the best practice for below controller case? Now it wont get user details before passing data to client.
I am doing a simply API endpoint to server side with NodeJS. Controller is making two queries to complete simply results as json object for client. Yes there is this useless run[] array but it is used when building whole event results :)
exports.runresults3 = function(req, res) {
// Route is passing ID
console.log('id', req.params.id);
// Test results object to fill
const resultsParams = {
run: [{
runid: {},
endtimeid: [],
userid: [],
endtime: [],
username: [],
}]
}
// Run objectid
resultsParams.run[0].runid = req.params.id;
// Get the endtimes by location/stage
Endtime.find({stage: req.params.id})
.then(data => {
// First loop
data.forEach(value => {
resultsParams.run[0].endtimeid.push(value._id);
resultsParams.run[0].userid.push(value.user);
resultsParams.run[0].endtime.push(value.endtime);
})
})
.then(() => {
// Second loop to get user details for results object
resultsParams.run[0].userid.forEach((userId, i) => {
TempUser.findById(userId)
.then(userdetails => {
console.log('userdetails.name', userdetails.name);
resultsParams.run[0].username.push(userdetails.name);
});
})
})
.then(() => {
res.json(resultsParams);
});
}
//////////// client side will get json as follows
{
"run": [
{
"runid": "5ae850d51717862590dc30d4",
"endtimeid": [
"5aec482d98555332145eccd3",
"5aec48c098555332145eccd6",
"5aec4a2c98555332145eccda",
"5aec4ab398555332145eccdd",
"5aec4bb998555332145ecce1",
"5aec4e42c3bcbb196c8474fc",
"5aec4e44c3bcbb196c8474fe",
"5aec4e45c3bcbb196c847500"
],
"userid": [
"5aec13b098555332145eccbe",
"5ae869c797e54a37f498c98f",
"5aec4a1298555332145eccd7",
"5aec4a1298555332145eccd7",
"5aec4ba698555332145eccde",
"5aec13a598555332145eccbc",
"5ae869c797e54a37f498c98f",
"5aec13b098555332145eccbe"
],
"endtime": [
24424,
3280,
11858,
38874,
5738,
40384,
50906,
36717
],
"username": []
}
]
}
exports.runresults3 = function(req, res) {
// Route is passing ID
console.log('id', req.params.id);
// Test results object to fill
const resultsParams = {
run: [{
runid: {},
endtimeid: [],
userid: [],
endtime: [],
username: [],
}]
}
// Run objectid
resultsParams.run[0].runid = req.params.id;
Endtime.find({stage: req.params.id})
.then(data => {
return Promise.all(data.map(record => {
TempUser.findById(record.user)
.then(userdetails => {
return {
endtimeid: record._id,
userid: record.user,
endtime: record.endtime,
username: userdetails.name
};
})
}))
.then(detailRecords => {
return detailRecords.reduce((acc, curr) => {
acc.endtimeid.push(curr.endtimeid);
acc.userid.push(curr.userid);
acc.endtime.push(curr.endtime);
acc.username.push(curr.username);
return acc;
},resultsParams.run[0]);
})
.then(() => {
res.json(resultsParams);
});
});
}
If you have a requirement to accumulate things into the "associative array" style of things in this result object, this is probably the way I would do it.
With that said, I would most likely try to return things as an array of run results - something more like the following...
{
"run": [
{
"runid": "5ae850d51717862590dc30d4",
"results": [
{
"endtimeid": "5aec482d98555332145eccd3",
"userid": "5aec13b098555332145eccbe",
"endtime": 24424,
"username": "User123"
},
{
"endtimeid": "5aec48c098555332145eccd6",
"userid": "5ae869c797e54a37f498c98f",
"endtime": 3280,
"username": "User234"
}
]
}
]
}
I believe the following code should do that.
exports.runresults3 = function(req, res) {
// Route is passing ID
console.log('id', req.params.id);
// Test results object to fill
const resultsParams = {
run: [{
runid: {},
results: []
}]
}
// Run objectid
resultsParams.run[0].runid = req.params.id;
Endtime.find({stage: req.params.id})
.then(data => {
return Promise.all(data.map(record => {
TempUser.findById(record.user)
.then(userdetails => {
return {
endtimeid: record._id,
userid: record.user,
endtime: record.endtime,
username: userdetails.name
};
})
}))
})
.then(results => resultsParams.run[0].results = reuslts)
.then(() => res.json(resultsParams));
}
There could certainly be more readability refactors to be done, and the fact that the last step of populating the result doesn't use the result of the previous flow troubles me a bit - but I try to avoid side effects whenever possible, so that may just be me.
Here is the edited versions as adviced :) But still missing the user details, it looks like promise.all isnt waiting "TempUser.findById(record.user)" query.
1 I created a fresh event, run, 3 users and 3 results.
2 Then I set some logs along db calls
3 Then I did a get request with postman
Log from localhost look like this after Postman GET request:
$ node app
Server started on 5000
Mongodb connected
id 5aeebd8a1b5ddf1424c25194
Get endtimes from database
[ { created: 2018-05-06T08:32:46.359Z,
_id: 5aeebdae1b5ddf1424c25199,
endtime: 23204,
user: 5aeebd751b5ddf1424c25191,
stage: 5aeebd8a1b5ddf1424c25194,
__v: 0 },
{ created: 2018-05-06T08:32:49.414Z,
_id: 5aeebdb11b5ddf1424c2519b,
endtime: 17149,
user: 5aeebd7b1b5ddf1424c25192,
stage: 5aeebd8a1b5ddf1424c25194,
__v: 0 },
{ created: 2018-05-06T08:32:51.769Z,
_id: 5aeebdb31b5ddf1424c2519d,
endtime: 10840,
user: 5aeebd7f1b5ddf1424c25193,
stage: 5aeebd8a1b5ddf1424c25194,
__v: 0 } ]
Set results to object
[ undefined, undefined, undefined ]
This should be the last one at chain?
Get user details from database
{ date: 2018-05-06T08:31:49.673Z,
_id: 5aeebd751b5ddf1424c25191,
name: 'Firstame Lastname1',
__v: 0 }
2. Get user details from database { date: 2018-05-06T08:31:55.562Z,
_id: 5aeebd7b1b5ddf1424c25192,
name: 'Firstame Lastname2',
__v: 0 }
2. Get user details from database { date: 2018-05-06T08:31:59.906Z,
_id: 5aeebd7f1b5ddf1424c25193,
name: 'Firstame Lastname3',
__v: 0 }
That is made by following code:
exports.runresults3 = function(req, res) {
// Route is passing ID
console.log('id', req.params.id);
// Test results object to fill
const resultsParams = {
run: [{
runid: {},
results: []
}]
}
// Each run objectid
resultsParams.run[0].runid = req.params.id;
Endtime.find({stage: req.params.id})
.then(data => {
console.log('1. Get endtimes from database', data);
return Promise.all(data.map(record => {
TempUser.findById(record.user)
.then(userdetails => {
console.log('2. Get user details from database', userdetails);
return {
endtimeid: record._id,
userid: record.user,
endtime: record.endtime,
username: userdetails.name
};
})
}))
})
.then(results => {
console.log('3. Set results to object', results);
console.log('This should be the last one at chain?');
resultsParams.run[0].results = results;
})
.then(() => res.json(resultsParams));
}