Can't render the page with ejs routing params and mongoose - javascript

Ok, I've got an user object:
{
posts: [
{
_id: 603f1c1b966c28291cb61d60,
title: '1111',
content: '1111',
__v: 0
},
{
_id: 603f5479c989d92fbc1d082c,
title: '222',
content: '222',
__v: 0
},
{
_id: 603f5e39ddcda01f281f8931,
title: '3333',
content: '3333',
__v: 0
}
],
_id: 603f1c14966c28291cb61d5f,
username: '1',
mail: '1#1.ru',
__v: 3
}
On the main page with all posts i click "read more" on single post,(button has a value of this post's id, i checked, at this point everything OK) and new page with single complete post should render, with two values: title and content, simple.
Here's what i do in app.js:
app.get("/posts/:postID", function(req, res) {
const postID = req.params.postID;
User.findById(req.user.id, (err, user)=>{
user.posts.find(post=>{
post._id === postID;
console.log(post);
});
});
At this point log return all 3 posts.Why? I've tried different ways, and as i remember, i've got post i needed, but still coudnt render data from it. Post page is:
<div class="block">
<h1><%= title %></h1>
<p><%= content %></p>
</div>
});
And when i tried:
res.render("post",{
title:post.title,
content: post.content
})
that didn't work.But, if i try for example:
res.render("post",{
title:req.user.posts[0].title,
content: req.user.posts[0].content
})
, that works.
Can anyone help me? And remember, User.find - data from mongoDB, req.user - same data, but stored in session when user logged in. I guess it,s not that good to render data from sesion. But, at this point any solution is acceptable, i stacked badly)

There are two approaches that you can take here:
Use an if condition and return only the matching element.
app.get("/posts/:postID", function (req, res) {
const postID = req.params.postID;
User.findById(req.user.id, (err, user) => {
user.posts.find(post => {
if (post._id === postID) {
console.log(post);
}
});
});
Return only the desired element directly from the query.
Excuse me for taking the liberty and modifying the code a bit.
const ObjectId = mongoose.Types.ObjectId; // Hoping you are using mongoose.
app.get("/posts/:postID", async function (req, res) {
try {
const postID = req.params.postID;
let post = await User.aggregate([
{
$match: {
"_id": ObjectId(req.user.id)
}
},
{
$unwind: "$posts"
},
{
$match: {
"posts._id": ObjectId(postID)
}
}
]);
if (post && post.length > 0) {
console.log(post[0])
return post; // or anyhow you want to format and return
}
} catch (exception) {
// Handle how you want.
}
});

Related

Update a record in database after delete with Sequelize Model and NodeJS

i created a controller that can delete but it is difference because it is not only delete records according ID, it also update the record after the API of delete is call.
Let me demonstrate my code below:
// delete user
exports.delete = (req, res) => {
const user_id = req.params.user_id;
// Find record by it is id to DELETE
User.findByPk(user_id).then((num) => {
if (num == 1) {
res.status(200).send({
message: "User was deleted successfully!",
data: null,
});
} else {
res.status(500).send({
message: `Cannot delete User with id=${user_id}. Maybe User was not found!`,
data: null,
});
}
});
//After delete must update the is_active field
User.update(
{
is_active: 0,
},
{
where: { is_active: null },
}
);
};
Now i have problem that i do not know how to make my API to work. So, i hope someone can help me to deal with this problem that how to update is_active MUST BE 0 after DELETE. I am appreciate it.
You just need to use async/await instead of then to wait for then result of findByPk before calling update:
exports.delete = async (req, res) => {
const user_id = req.params.user_id;
// Find record by it is id to DELETE
const user = await User.findByPk(user_id)
if (user) {
//After delete must update the is_active field
await User.update(
{
is_active: 0,
},
{
where: { is_active: null },
}
);
res.status(200).send({
message: "User was deleted successfully!",
data: null,
});
} else {
res.status(500).send({
message: `Cannot delete User with id=${user_id}. Maybe User was not found!`,
data: null,
});
}
};
This way you have a straight forward code like you usually write when it's synchronous.

How can I update 2 collections at the same time using Node.js/Mongoose/MongoDB

Thank you for taking your time reading this.
I am making blog app with Node.js/Mongoose/MongoDB. Currently, I'm struggling to figure out how to update 2 collections at the same time.
My userSchema has arrays of postSchema, and I would like to update both users and posts collections whenever I update documents.
My code is here:
const postSchema = new mongoose.Schema({
title: String,
content: String,
author: String
});
const Post = mongoose.model('Post', postSchema);
const userSchema = new mongoose.Schema({
username: String,
password: String,
displayName: String,
provider: String,
posts: [postSchema],
drafts: [postSchema]
});
const User = mongoose.model('User', userSchema);
app.post('/edit/:title', function (req, res) {
Post.findOneAndUpdate({ title: req.params.title }, {
title: req.body.title,
content: req.body.content
}, function (error, post) {
if (error) {
console.log(error);
} else {
res.redirect('/dashboard');
}
});
});
At the moment, my code only updates posts collection, and arrays of postSchema inside users collection stay the same. Could anyone help me how to solve this problem?
You can do it 2 ways
Option 1
.then() & .catch() block
Post.findOneAndUpdate({
Do your stuff here
}).then((result)=>{
Do your stuff here with result from step above
}).catch((err)=>{
Handle Error
});
Option 2
Use async/ await
async function (req, res) {
const postResult = await Post.findOneAndUpdate({ title: req.params.title }, {
title: req.body.title,
content: req.body.content
});
const userResult = await User.findOneAndUpdate({Do some stuff here});
if(!postResult || !userResult){
return new Error(...)
}
return
Since there is not much of code shared this cannot be used as is.. but the logic behind these options will remain the same even in your code ..

How to check if there are no more documents to update using findOneAndUpdate

So I am learning CRUD for a school project and I followed a tutorial that was really useful. However, when I completed it I noticed that when there are no more quotes to update, it still updates quotes. How can I change this so that it will stop updating quotes that arent even there?
app.put('/quotes', (req, res) => {
quoteCollection.findOneAndUpdate(
{ name: 'Yoda' },
{
$set: {
name: req.body.name,
quote: req.body.quote
}
},
{upsert: true}
)
.then(result => {
//The if block that i am trying
if (result.deletedCount === 0) {
return res.json('No quote to delete')
}
})
.catch(error => console.error(error))
})
Why are you passing {name: "Yoda}? This route is supposed to only update the quote with "Yoda" as its name? If not, then you need to grab from the request object the quote that should be updated.
I tried to create a different version, based on the assumption that the quote that should be updated will come from the req.body:
app.put("/quotes", async (req, res) => {
//Grab the name/id/identifier for the quote you want to update from the body
const query = req.body.name;
// Try to update the document on the database
try {
const result = await quoteCollection.findOneAndUpdate(
query,
{
name: req.body.name,
quote: req.body.quote,
},
{
upsert: true,
new: true,
}
);
// If it worked, it will return the updated quote
res.status(200).json({
status: 200,
data: {
result,
},
});
} catch (err) {
res.status(400).json({
status: 400,
message: "Something went wrong",
});
}
});

MongoDB: remove skill from user document

So I have an issue. I have a MongoDB user document and this document has a property skills, which is an array filled with objects.
What I want to do now is the following. The client sends a request to delete one of these skills. So I send the skill ID and then I want to remove the skill from the user. How could I do this?
What I currently have: every item in the skills property array has a skill property which is an objectID. When this objectID matches the objectId that the client sent, then we want to remove this skill from the user
const removeSkill = async (req, res) => {
try {
const { userId, params: { skillId } } = req;
const user = await User.findByIdAndUpdate({ _id: userId }, {
$pull: { "skills.skill": skillId }
}, {
new: true
});
return res.status(200).json({
message: 'succesfully removed skill',
user
});
} catch (err) {
return sendErr(res, err);
}
};
What the user mongodb document looks like
The error I get
:no_entry: Error:
MongoError: Cannot use the part (skill) of (skills.skill) to traverse the element ({skills: [ { _id: ObjectId('5c8729be12e1cc05c04ea182'), name: "javascript", skill: ObjectId('5c8729be
12e1cc05c04ea181'), points: 22 }, { _id: ObjectId('5c8729dc12e1cc05c04ea184'), name: "node.js", skill: ObjectId('5c8729dc12e1cc05c04ea183'), points: 14 }, { _id: ObjectId('5c872a6c12e1c
c05c04ea186'), name: "css", skill: ObjectId('5c872a6c12e
First of all you shouldn't be using 2 Object ID in one object. The beauty of MongoDB is auto generated ObjectID. For your question (using skill ObjectID)
const user = await User.findByIdAndUpdate(
{ _id: userId },
{ $pull: { "skills": { skill: skillId }}},
{ new: true}
);
I believe u are receiving ObjectID from your params. If so,Something like this should help.

Populate subdocument in parent document Array Mongoose

I'm very new to JavaScript and Mongoose. I'm building a small project using express, mongoose and node.js.
I have a mongoose model - Client that has an Array of Transactions
var Client = mongoose.model('Client', {
name: {
type: String,
required: true,
minlength: 1
},
email: {
type: String
},
phone: {
type: Number
},
createdAt: {
type: Number,
default: null
},
transactions: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Transaction' }],
_creator: {
type: mongoose.Schema.Types.ObjectId,
required: true
}
});
module.exports = {Client};
This is Transaction model:
var Client = require('./client');
var Transaction = mongoose.model('Transaction',{
_creator : { type: mongoose.Schema.Types.ObjectId, ref: 'Client' },
amount : {type: Number, min: 0},
date : {type: Number,default: null},
total: {type: Number,default: null}
});
module.exports = {Transaction};
When I POST a new Transaction it goes through and saves in db:
app.post('/clients/:id/transactions', authenticate, (req, res) => {
var id = req.params.id;
var transaction = new Transaction({
amount: req.body.amount,
date: new Date().getTime(),
total: req.body.total,
_creator: req.params.id
})
if (!ObjectID.isValid(id)) {
return res.status(404).send();
}
transaction.save().then((doc) => {
Client.findOneAndUpdate({
_id: id,
_creator: req.user._id,
transactions: req.body.transaction
});
res.send(doc);
}, (e) => {
res.status(400).send(e);
});
});
I am also able to GET all the transactions associated with the client:
app.get('/clients/:id/transactions', authenticate, (req, res) => {
var id = req.params.id;
if (!ObjectID.isValid(id)) {
return res.status(404).send();
}
Transaction.find({
_creator: id
}).then((transactions) => {
res.send({transactions});
}).catch((e) => {
res.status(400).send();
});
});
But when I make a GET call to '/clients' - Array of Transactions is empty:
{
"clients": [
{
"_id": "1095d6de3867001108b803",
"name": "Peter",
"email": "peter#gmail.com",
"phone": 1232321,
"_creator": "5321df6d57868ec7001108b801",
"__v": 0,
"transactions": [],
"createdAt": null
} ]
}
And this is the GET call to /clients
app.get('/clients', authenticate, (req, res) => {
Client.find({
_creator: req.user._id,
})
.populate('transactions.transaction')
.then((clients) => {
res.send({clients});
}, (e) => {
res.status(400).send(e);
console.log('Unable to get clients', e);
})
});
I know that I'm likely doing something completely wrong but I don't know where I need to look for my mistake. Please help!
I would check if the client exist before adding a transaction. A transaction needs a client first.
Forewarn, I'm not a fan of then and catch so this answer does not use it. I normally use async.js when dealing with multiple asynchronous operations.
Anyways, I would do it like
app.post('/clients/:id/transactions', authenticate, (req, res) => {
Client.findOne({ _id: req.params.id }, (err, client) => {
if (err)
return res.status(400).send(err);
if (!client)
return res.status(400).send(new Error('No client'));
Transaction.create({
amount: req.body.amount,
date: new Date(), // I don't think you need .getTime()
total: req.body.total,
_creator: client._id
}, (err, transaction) => {
if (err)
return res.status(400).send(err);
client.transactions.push(transaction._id);
client.save(err => {
if (err)
return res.status(400).send(err);
res.json(transaction);
});
});
});
});
Good idea to also turn on debugging mode to see your queries: mongoose.set('debug', true).
You might also find using timestamps option for Transaction schema more useful than explicitly using date field
To get clients with their transactions
app.get('/clients', authenticate, (req, res) => {
Client.find({ _creator: req.user._id }).populate('transactions').exec((err, clients) => {
if (err)
return res.status(400).send(err);
res.json(clients);
});
});
so first of all i don't exactly know what _creator key in Client model representing, it's probably user identifier who has some clients, but if I'm wrong please correct me.
Honestly I don't know why you are making two way document connection, (keeping client in transactions, and also keeping transactions in clients) in my opinion first option is better for mongodb and using that you can easily get transaction's list with find, or mongodb aggregation, but you can't get data using populate.
In second option you need to remember that one document could have maximum 16MB. And also keeping thousands of transactions in one array is not well for performance. Think about example that you have 5000 transaction and you want to show list with pagination (50 records per page), with array option you have to get whole document, and splice array to 50 records. In first option you could use mongodb skip and limit. Please think about it.
Returning to question, mistake you are doing is here:
transaction.save().then((doc) => {
Client.findOneAndUpdate({
_id: id,
_creator: req.user._id,
transactions: req.body.transaction
});
res.send(doc);
Here you don't exactly say how this document should have to updated about.
Mongoose in method findOneAndUpdate using mongodb findAndModify method. But params are used from update.
https://docs.mongodb.com/manual/reference/method/db.collection.update/
And also documentation says that you what params like:
Query#findOneAndUpdate([query], [doc], [options], [options.passRawResult], [options.strict], [callback])
So first query param is mongo query to find one document in database, next param is object with updating query, and after that you could send some additional options in third param. So your code should looks like this:
transaction.save().then((doc) => {
Client.findOneAndUpdate({
_id: id,
_creator: req.user._id,
}, {
$addToSet: {
transactions: doc._id,
}
});
res.send(doc);
You could use addToSet or push both are putting element into array, but addToSet avoiding duplicates in array. And as you se we push new transaction identifier into this array. And after all you only populate transaction key.
I hope I helped. If you have any questions please ask.

Categories