mongoose find and return res.status.json - node.js

I'm developing an API that shows the bulletin boards list.
And it also shows one of the latest posts at the same time.
In controller I coded like this
exports.boards_get_all = (req, res, next)=>{
Board.find()
.exec()
.then(boards=>{
res.status(200).json({
count: boards.length,
boards: boards.map(board=>{
return {
board: {
id:board._id,
name: board.name,
order: board.order
},
post : Post.findOne({boardId:board._id})
.exec()
.then(posts=>{
console.log(posts)
return posts;
}
)
}
})
})
})
}
I use findOne() and try to return what Post found
but return post didn't work. just return empty value.
result :
{
"count": 1,
"boards": [
{
"board": {
"id": "5eb23f1fed38dc5dfc2debfd",
"name": "QnA board",
"order": 1
},
"post": {}
}
]
}
I think I took the wrong approach about using findOne()...
I want to get result like this
{
"count": 1,
"boards": [
{
"board": {
"id": "5eb23f1fed38dc5dfc2debfd",
"name": "QnA board",
"order": 1
},
"post": {
_id: 5eb364a27989ab6f414fcdb1,
userId: 5eb2aad669738d67b5497f3a,
boardId: 5eb23f1fed38dc5dfc2debfd,
type: 1,
title: 'this is a latest post in QnA board',
content: 'Sample content',
like: 0,
comment: 0,
view: 1,
date: 2020-05-07T01:30:10.957Z,
thumb: 'pic url',
__v: 0
}
}
]
}

The post is {} because the res.status(200).json() is executed and returned before the findOne callback returns a value.
To fix this, you need to get the post documents from the database before calling res.status(200).json().

Related

How can I get only the array element as output instead of whole object in MongoDB?

Below is my code to display review array data which is part of the restaurant collection object:
async get(reviewId) {
const restaurantsCollection = await restaurants();
reviewId = ObjectId(reviewId)
const r = await restaurantsCollection.findOne(
{ reviews: { $elemMatch: { _id : reviewId } } },
{"projection" : { "reviews.$": true }}
)
return r
}
My object looks like:
{
_id: '6176e58679a981181d94dfaf',
name: 'The Blue Hotel',
location: 'Noon city, New York',
phoneNumber: '122-536-7890',
website: 'http://www.bluehotel.com',
priceRange: '$$$',
cuisines: [ 'Mexican', 'Italian' ],
overallRating: 0,
serviceOptions: { dineIn: true, takeOut: true, delivery: true },
reviews: []
}
My output looks like:
{
"_id": "6174cfb953edbe9dc5054f99", // restaurant Id
"reviews": [
{
"_id": "6176df77d4639898b0c155f0", // review Id
"title": "This place was great!",
"reviewer": "scaredycat",
"rating": 5,
"dateOfReview": "10/13/2021",
"review": "This place was great! the staff is top notch and the food was delicious! They really know how to treat their customers"
}
]
}
What I want as output:
{
"_id": "6176df77d4639898b0c155f0",
"title": "This place was great!",
"reviewer": "scaredycat",
"rating": 5,
"dateOfReview": "10/13/2021",
"review": "This place was great! the staff is top notch and the food was delicious! They really know how to treat their customers"
}
How can I get the output as only the review without getting the restaurant ID or the whole object?
So the query operators, find and findOne do not allow "advanced" restructure of data.
So you have 2 alternatives:
The more common approach will be to do this in code, usually people either use some thing mongoose post trigger or have some kind of "shared" function that handles all of these transformations, this is how you avoid code duplication.
Use the aggregation framework, like so:
const r = await restaurantsCollection.aggregate([
{
$match: { reviews: { $elemMatch: { _id : reviewId } } },
},
{
$replaceRoot: {
newRoot: {
$arrayElemAt: [
{
$filter: {
input: "$reviews",
as: "review",
cond: {$eq: ["$$review._id", reviewId]}
}
},
0
]
}
}
}
])
return r[0]

POST method an array in nodejs

im having trouble in creating an array in postman.
What I want is to post this in the request.body(postman/insomnia)..
{
"nama": "tests",
"afco_id": 2,
"business_area_id": 10,
"approval_level": [
{
"sequence_level": 1,
"user_id": 1
},
{
"sequence_level": 2,
"user_id": 2
},
{
"sequence_level": 3,
"user_id": 3
}
]
}
and this is my controller code:
const postApprovalMaster = async (req, res) => {
return (
db.Bb_t2_Approval.create(
{
nama: req.body.nama,
afco_id: req.body.afco_id,
business_area_id: req.body.business_area_id,
approval_level: [req.body]
},
{
include: [
{
model: db.Bb_t3_Approval_Level,
as: "approval_level",
},
{
model: db.Bb_t1_Organisasi,
as: "afco",
},
{
model: db.Bb_t1_Organisasi,
as: "business_area",
},
],
}
).then((result) =>
res.status(201).json({
data: result,
status: true,
message: "Created new approval master success",
})
)
.catch((err) => {
res.status(400).json({
status: false,
message: err.message,
})
})
)
}
whenever i hardcoded the req.body into the controllers approval_level, it works just fine. but i dont want it like that
can anyone help? thanks a lot I appreciate it
You can either try this
Solution 1
or if this doesnt work with node.js you can also try to just write the array attribute like this.
Solution 2
One of those should work.
I can't comment and I'd love to help but can you try to give more examples or explain a little more #Hafizh Farhandika

Node: Updating DB with PUT not working for MongoDB

My GET returns an array of 2 simple objects from the DB:
[
{
"_id": "60491b5741893d23216d2de3",
"text": "test`",
"score": 19,
"createdAt": "2021-03-10T19:17:43.809Z"
},
{
"_id": "604947c7b3a7ed28c43c05b7",
"text": "HELLO",
"score": 22,
"createdAt": "2021-03-10T22:27:19.739Z"
}
]
In Postman, I am trying to do a PUT to /604947c7b3a7ed28c43c05b7 to update that post. In the body, I am sending:
{
"text": "Updated post test",
"score": 100
}
and my node route looks like this:
router.put('/:id', async(req,res) => {
const posts = await loadPostsCollection();
const post = {};
if (req.body.text) post.text = req.body.text;
if (req.body.score) post.score = req.body.score;
await posts.findOneAndUpdate(
{ _id: req.params.id },
{ $set: post },
{ new: true }
);
res.status(200).send();
})
I am getting a success message back but when I do a GET to see the array, the value hasn't changed for that post.
I am assuming you're using the native mongodb node driver. In mongoose findOneAndUpdate() will not actually execute the query unless a callback function is passed. I don't know this with full certainly, but it sounds like the native driver works the same way. So you would have to rewrite your code like so:
posts.findOneAndUpdate(
{ _id: req.params.id },
{ $set: post },
{ new: true },
result => {console.log(result)}
);
Another way to do it is by appending .then() to the end, because according to the docs, it return a promise if no callback is passed. So here is how I would do it:
await posts.findOneAndUpdate(
{ _id: req.params.id },
{ $set: post },
{ new: true }
).then(r => r);

How can I push an object to a nested array of objects in mongoDB and expressjs

I have the following Board object:
{
"boardMembers": [
"5f636a5c0d6fa84be48cc19d"
],
"boardLists": [
{
"cards": [],
"_id": "5f6387e077beba2e3c15d15a",
"title": "list one",
"__v": 0
}
],
"_id": "5f63877177beba2e3c15d159",
"boardName": "board1",
"boardPassword": "123456",
"boardCreator": "5f636a5c0d6fa84be48cc19d",
"g_createdAt": "2020-09-17T15:57:37.616Z",
"__v": 2
}
as you can see there is a "boardLists" array, I want to make a post request that post a card to a list inside that array with specific ID.
that my code:
router.post("/add-task/:id", auth, boardAuth, async (req, res) => {
const listId = req.params.id;
try {
const board = await Board.findOne({ _id: req.board._id });
if (!board) return res.status(404).send("no such board");
const list = await List.findOne({ _id: listId });
if (!list) return res.status(404).send("List not found");
const task = new Task({
text: req.body.text,
});
Board.updateOne(
{ _id: req.board._id, "boardLists._id": listId },
{ $push: { "boardLists.$.cards": task } }
);
await board.save();
res.send(board);
} catch (err) {
console.log(err);
}
});
Now the problem is when I make the request call in postman its does the push inside the specific list that i want but its not saving the push inside the parent board object: when do a get request to that object i get an empty cards array.
like that:
{
"boardMembers": [
"5f636a5c0d6fa84be48cc19d"
],
"boardLists": [
{
"cards": [],
"_id": "5f6387e077beba2e3c15d15a",
"title": "list one",
"__v": 0
}
],
"_id": "5f63877177beba2e3c15d159",
"boardName": "board1",
"boardPassword": "123456",
"boardCreator": "5f636a5c0d6fa84be48cc19d",
"g_createdAt": "2020-09-17T15:57:37.616Z",
"__v": 2
}
Why its not saving the push in the board object?
Mongoose is weird. You need to mark the field as modified, otherwise the changes won't be saved. Try this :
board.markModified("boardLists");
await board.save();

Using pull in mongoose model

Should this work? I am trying to remove a single subdocument (following) from a document (this) in the UserSchema model.
UserSchema.methods.unFollow = function( id ) {
var user = this
return Q.Promise( function ( resolve, reject, notify ) {
var unFollow = user.following.pull( { 'user': id } )
console.log( unFollow )
user.save( function ( error, result ) {
resolve( result )
})
})
}
These are the schemas:
var Follows = new mongoose.Schema({
user: String,
added: Number
})
var UserSchema = new mongoose.Schema({
username: {
type: String,
required: true,
unique: true
},
following: [ Follows ]
})
user-controller.js
/*
Unfollow user.
*/
exports.unFollow = function ( req, res ) {
User.findOne( { token: req.token }, function ( error, user ) {
user.unfollow( req.body.id )
.onResolve( function ( err, result ) {
if ( err || !result ) return res.status( 500 ).json( "User could not be unfollowed." )
return res.status( 200 ).json( "User unfollowed." )
})
})
}
user-model.js
/*
Unfollow a user.
*/
UserSchema.method( 'unfollow', function unfollow ( id ) {
this.following.pull( { user: id } )
return this.save()
})
You generally assign methods using the method function:
UserSchema.method('unFollow', function unFollow(id) {
var user = this;
user.following.pull({_id: id});
// Returns a promise in Mongoose 4.X
return user.save();
});
Also, as noted, you don't need to use Q as save will return a mongoose promise.
UPDATE: Mongoose's array pull method will work with matching primitive values but with subdocument objects it will only match on _id.
UPDATE #2: I just noticed your updated question shows that your controller is doing a lookup first, modifying the returned document and then saving the document back to the server. Why not create a static rather than a method to do what you want? This has the added bonus of being a single call to the DB rather than two per operation.
Example:
UserSchema.static('unfollow', function unfollow(token, id, cb) {
var User = this;
// Returns a promise in Mongoose 4.X
// or call cb if provided
return User.findOneAndUpdate({token: token}, {$pull: {follows: {user: id}}}, {new: true}).exec(cb);
});
User.unfollow(req.token, req.body.id).onResolve(function (err, result) {
if (err || !result) { return res.status(500).json({msg: 'User could not be unfollowed.'}); }
return res.status(200).json({msg: 'User unfollowed.'})
});
Bonus follow static:
UserSchema.static('follow', function follow(token, id, cb) {
var User = this;
// Returns a promise in Mongoose 4.X
// or call cb if provided
return User.findOneAndUpdate({token: token}, {$push: {follows: {user: id}}}, {new: true}).exec(cb);
});
User.follow(req.token, req.body.id).onResolve(function (err, result) {
if (err || !result) { return res.status(500).json({msg: 'User could not be followed.'}); }
return res.status(200).json({msg: 'User followed.'})
});
NOTE: Used in "mongoose": "^5.12.13".
As for today June 22nd, 2021, you can use $in and $pull mongodb operators to remove items from an array of documents :
Parent Document :
{
"name": "June Grocery",
"description": "Some description",
"createdDate": "2021-06-09T20:17:29.029Z",
"_id": "60c5f64f0041190ad312b419",
"items": [],
"budget": 1500,
"owner": "60a97ea7c4d629866c1d99d1",
}
Documents in Items array :
{
"category": "Fruits",
"bought": false,
"id": "60ada26be8bdbf195887acc1",
"name": "Kiwi",
"price": 0,
"quantity": 1
},
{
"category": "Toiletry",
"bought": false,
"id": "60b92dd67ae0934c8dfce126",
"name": "Toilet Paper",
"price": 0,
"quantity": 1
},
{
"category": "Toiletry",
"bought": false,
"id": "60b92fe97ae0934c8dfce127",
"name": "Toothpaste",
"price": 0,
"quantity": 1
},
{
"category": "Toiletry",
"bought": false,
"id": "60b92ffb7ae0934c8dfce128",
"name": "Mouthwash",
"price": 0,
"quantity": 1
},
{
"category": "Toiletry",
"bought": false,
"id": "60b931fa7ae0934c8dfce12d",
"name": "Body Soap",
"price": 0,
"quantity": 1
},
{
"category": "Fruit",
"bought": false,
"id": "60b9300c7ae0934c8dfce129",
"name": "Banana",
"price": 0,
"quantity": 1
},
{
"category": "Vegetable",
"bought": false,
"id": "60b930347ae0934c8dfce12a",
"name": "Sombe",
"price": 0,
"quantity": 1
},
Query :
MyModel.updateMany(
{ _id: yourDocumentId },
{ $pull: { items: { id: { $in: itemIds } } } },
{ multi: true }
);
Note: ItemIds is an array of ObjectId. See below :
[
'60ada26be8bdbf195887acc1',
'60b930347ae0934c8dfce12a',
'60b9300c7ae0934c8dfce129'
]

Resources