Proper Way To Ensure All Save Methods Work: node.js, MongoDB - javascript

I am quite new to MongoDB and Node.JS, and I have a question about what is the proper way to implement multiple save calls in the same route and ensure that they all work, or none do. What I want, is that if one save fails, they both do. Here is some code. It works as expected with no problems, but I have a feeling that it is bad practice.
My Post Schema, as you can see, it is just some text, and then an array of pointers to another object called Comment.
var postSchema = mongoose.Schema({
text: String,
comments : [{
type : mongoose.Schema.Types.ObjectId,
ref : "Comment"
}]
});
My Comment Schema. All it is, is just some text.
var commentSchema = mongoose.Schema({
commentText: String
});
Now, here is my route...
apiRoutes.post("/testCreateComment", function(request, response, next){
var commentText = request.body.commentText;
var testID = request.body.testID
if (!commentText || !testID) //ensures all data is there.
return next(new Error("Not all the information required was provided."));
var newComment = new Comment({
commentText : commentText
})
newComment.save(function(err) { //first save the new TestComment.
if (err) return next(err);
Post.findById(testID, function(err, found){ //second find the Post object.
if (err) return next(err);
found.comments.push(newComment)
found.save(function(err){ //third, save the Post object with the comment added to the array.
if (err) return next(err);
response.json(newComment.getReturn()); // returns some data to be sent
})
})
})
})
In this route, there are three methods that must be successful for the entire operation to work. Now, for instance, save that the first save works, but for whatever reason the findById is unsuccessful. In this circumstance, I would have a comment floating around in the database that is useless.
My question is, what is the proper way to ensure that all three operations are successful and ensure that the problem described above cannot possibly happen?

Related

Confusing error message using updateOne() function on a mongodb collection

To summarize, I have a mongodb database with a 'groupcollection' in it. One of the attributes is called 'deleted' and it has values either true or false. I want to update the value of 'deleted' for a specific document using 'groupname' as the query attribute. However, when I try the code below I receive the error "TypeError: collection.updateOne is not a function"
router.post('/deletegroup', function(req, res) {
var db = req.db;
var collection = db.get('groupcollection');
var filter = {"groupname" : req.body.groupname};
var updates = { $set: {"deleted" : true} };
collection.updateOne(filter, updates, function(err) {
if (err) {
// If it failed, return error
res.send("There was a problem deleting the group from the database.");
}
else {
// And forward to success page
res.redirect("grouplist");
}
});
});
I've read the documentation on updateOne() for Node.js from mongoDB and I can't seem to figure out the reason for the error. Also, I am still very new to javascript/nodejs/mongo so I would greatly appreciate more informative answers!
The solution I came up with was using unique IDs for each group and instead of using updateOne() just using update() and having the unique ID as the query to make sure that I don't modify groups with the same name

Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client. Attempting to write Mongo ObjectID to cookies

I will preface this by saying I understand it is not best practice to put a userID in cookies, but this is an extremely simple application that is just supposed to display to the same user the number of times they have visited the site, so I'm just rolling with this implementation to keep things simple.
I'm using express, node, and mongoDB to handle this. What I'm trying to do is create and save a new instance of my User class, and then in the promise chain (where the Mongo ObjectID is returned), I'd like to write that ID to cookies so I can keep track of how many times they have come to the site.
It seems like I am receiving the Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers because I am trying to set cookies in the then statement which is completed after the headers are already sent off. I don't really understand this though, because I don't call setHeaders anywhere else.I have thought about creating a randomly generated number within the server, but that seems clunky as I can't guarantee it won't generate an existing ID. Does anyone know how I can get around this issue, or am I taking the complete wrong approach here?
User schema:
var UserSchema = new Schema({
timesSeen: { type: Number, default: 0 },
});
The problem code
router.get("/", function (req, res, next) {
// Read cookies
let cookies = parseCookies(req.headers.cookie);
// If userID cookies is not set, this is a new user (or one visiting from another browser)
if (cookies.userID === undefined) {
// Create a new instance of a user and save to mongoDB
var newUser = new User({});
newUser
.save()
.then((newUser) => {
console.log("newUser");
// This is the line that is giving me problems.
res.setHeader("Set-Cookie", [
`userID=${newUser._id}`,
"timesSeen=0",
]);
})
.catch((err) => {
if (err) {
console.log(err);
return;
}
});
} else {
let timesSeen = Number(cookies.timesSeen) + 1;
// let timesSeen = parseInt(cookies.timesSeen)++;
// console.log("times", timesSeen);
res.setHeader("Set-Cookie", [`timesSeen=${timesSeen}`]);
res.render("index", {
title: "Express",
});
}
});

Node.js/mongoose - sub-document in a array won't delete/remove

So this is my first on-my-own node.js project after finishing up an online Bootcamp. Running into trouble removing a sub-doc in my array. Here's some code, I hope I provide enough info for someone to help me out.
models:
var productSchema = new mongoose.Schema({
name: String,
type: String,
location: String
});
var clientSchema = new mongoose.Schema({
name: String,
address: String,
contactinfo: String,
products:[]
});
And this is my post route that is adding the product to the client, which works great:
//Add New Product
app.post("/clients/:id/products", middleware.isLoggedIn, function(req, res){
Client.findById(req.params.id, function(err, client) {
if(err){
console.log(err);
req.flash('error', "We cannot find the Client!!!");
return res.redirect("/clients/" + req.params.id + "/products/new");
}
Product.create(req.body.product, function(err, product){
if(err){
req.flash('error', "There was an error adding the product to the user, try again");
} else{
client.products.push(product);
client.save();
req.flash('success', "You have added a New Product");
res.redirect('/clients/' + req.params.id +'/products/new');
}
});
});
});
My delete route is my problem child. It deletes the product, but I can't seem to get it out of the array at all. I did some research and tried the following:
client.products.find({_id:req.params.product_id}).remove()
client.products.id(req.params.product_id).remove()
client.products.pull({_id: req.params.product_id})
client.find({products:{_id: req.params.product_id}}).remove()
using client.save() right after each
I get errors or it deletes the client,
but never deletes the product from the array. Any help would be great or if there's a better way to do this, that would be great too. Tried for a week before turning for help, so open for feedback from skilled developers.
oh here is my last delete route, think I'm going to disable until I found a fix , so I can continue my project.
//Delete a Product
app.delete("/clients/:id/products/:product_id", middleware.isLoggedIn, function(req, res){
Product.findByIdAndRemove(req.params.product_id, function(err){
if(err){
console.log(err);
} else {
console.log("Should be deleted now!");
Client.findById(req.params.id, function(err, client) {
if(err){
console.log(err);
}
console.log(client.products.length);
client.find({products: {_id: req.params.product_id}}).remove();
client.save();
console.log(client.products.length);
res.redirect("/clients/");
});
}
});
});
The length I used to see if anything changed and it never did.
First, your method requires a objectID. Try like this and see if it works:
Product.findByIdAndRemove(ObjectId(req.params.product_id)
Nevermind, it seems that the issue here is my code. Instead of pushing a ref in my client schema for the products array I pushed the product info directly in the array. Then in my app in one area I'm accessing the data from the Product collection, but in another part of my app I'm accessing the same info from the client collection through the products array. Based on my training(Other course apps I created) it seems that the ref doesn't get remove it just can't refer to the collection since it's no longer in the collection.
Thank you Laurentiu & Federico for looking at this and providing some helpful information. sorry about any headaches this may have caused you.
Looked into documents and it won't delete. It deletes the content not the ID, so it stays in the MongoDB Database.
router.delete('/inventario/:_id', function(req, res){
Propiedades.findByIdAndDelete(req.params._id, function(err,){
if (err){
res.redirect('/inventario/');
} else {
res.redirect('/inventario/');
}
})
});

associating a GridFS-Stream file to a Schema with Mongoose

I am writing an API for my application, using Mongoose, Express, and GridFS-Stream. I have a Schema for the articles the user will create:
var articleSchema = mongoose.Schema({
title:String,
author:String,
type: String,
images: {type: Schema.Types.ObjectId, ref: "fs.files"},
datePublished: { type: Date, default: Date.now },
content: String
})
var Article = mongoose.model("article", articleSchema, "articles");
and my grid-fs set up for when a user uploads an image:
api.post('/file', fileUpload.single("image"), function(req, res) {
var path = req.file.path;
var gridWriteStream = gfs.createWriteStream(path)
.on('close',function(){
//remove file on close of mongo connection
setTimeout(function(){
fs.unlink(req.file.path);
},1000);
})
var readStream = fs.createReadStream(path)
.on('end',function(){
res.status(200).json({"id":readStream.id});
console.log(readStream);
})
.on('error',function(){
res.status(500).send("Something went wrong. :(");
})
.pipe(gridWriteStream)
});
Right now it's set up to when the user chooses an image, it automatically uploads it via gridfs-stream, puts it in a temp folder, then deletes it when it is uploaded to the mongo server, and in the console returns what the ObjectId is. Well thats all find and dandy, but we need to associate this ID with the articleSchema, so when we call that article in the app, it will display the associated image.
on our creation/update of an article when the user hits submit:
createArticle(event) {
event.preventDefault();
var article = {
type: this.refs.type.getValue(),
author: this.refs.author.getValue(),
title: this.refs.title.getValue(),
content: this.refs.pm.getContent('html')
};
var image = {
images: this.refs.imageUpload.state.imageString
};
var id = {_id: this.refs.id.getValue()};
var payload = _.merge(id, article, image);
var newPayload = _.merge(article, image)
if(this.props.params.id){
superagent.put("http://"+this.context.config.API_SERVER+"/api/v1.0/article/").send(payload).end((err, res) => {
err ? console.log(err) : console.log(res);
});
} else {
superagent.post("http://"+this.context.config.API_SERVER+"/api/v1.0/article").send(newPayload).end((err, res) => {
err ? console.log(err) : console.log(res);
this.replaceState(this.getInitialState())
this.refs.articleForm.reset();
});
}
},
So what I need it to do, is call the ID, of the image I just uploaded to the images section of my schema when the user hits submit on the creation of an article. I've tried doing a readstream on submit, but again, the problem is I can't get the ID, or the filename, to be able to associate it.
They are getting stored in the mongo database, it creates fs.files and fs.chunks, but for the life of me I can't figure out how to get that data and attach it to a schema, or just even get the data out, without knowing the ObjectId.
So how do I call out the objectid from fs.files or fs.chunks to attach it to the schema? and in the schema how do I reference the fs.files or chunks? so it knows what the objectid is associated with?
I can provide anymore data, if what I have is to vague, I have a nasty habit of doing that. sorry.
So I ended up solving my problem, might not be the best solution, but it works until I can get a better solution.
in the API changed
res.status(200).json({"id":readStream.id});
to
res.status(200).send(readStream.id);
in my component, I then set the state to the response.body, which will set the state of the id of the image uploaded. So in the main view, i reference the image uploading component, and set the image state of my view to the id state of my component, and viola, I now have the id in my database, associated with the newly created article.
the problem i then ran into was, it didn't know what to reference. so I attached the API URL to the id, and it acts like it is referencing a URL img, and renders the image correctly.
Again, this may not be the best way to go about this, in fact, I am pretty sure it isn't, but It is whats working for now until I can either reference the database correctly, or create a new component that just stores all the images on server and reference them that way, much like wordpress.

Parse Cloud Code. Unable to define relation

I am calling this function from my android app,
Parse.Cloud.define('addFriendRequest', function(request, response) {
var userObjectId = request.params.userObjectId;
var User = Parse.Object.extend('_User'),
user = new User({ objectId: userObjectId });
var relation = Parse.Relation(user, 'friendRequests');
relation.add(request.user);
Parse.Cloud.useMasterKey();
user.save().then(function(user) {
response.success("Successfully added friend Request");
}, function(error) {
response.error("An error has occurred")
});
});
And an errror is being thrown of type
TypeError: Cannot call method 'add' of undefined at main.js:10:11
I am relatively new to javascript so any advice would be great. Also the relation friendRequests exists already.
This should work.
Parse.Cloud.define('addFriendRequest', function(request, response) {
Parse.Cloud.useMasterKey();
var userObjectId = request.params.userObjectId;
var user = Parse.User.createWithoutData(userObjectId);
var relation = user.relation('friendRelation');
relation.add(request.user);
user.save().then(function(user) {
response.success("Successfully added friend Request");
}, function(error) {
response.error("An error has occurred")
});
});
Some clarification of what is going on here and why your code did not work.
First of all, it is a good practice to put Parse.Cloud.useMasterKey(); right in the beginning of the function.
I see that you have the objectId of user in the beginning and that you would like to use the user based on its objectId. I order to do that, you should use Parse.User.createWithoutData('yourObjectIdHere') method. What you are actually doing for this step, is that you are creating a new user, rather then creating a reference for the one you already have in the database.
If you want to get a relation of a particular object (User object in your case) your use Parse.Object.relation('putRelationKeyHere'). You are actually creating a new relation instead of accessing the existing one.
I would recommend you to read Parse Javascript Guide in order to learn the recommended techniques.

Categories