Mongoose findOneAndUpdate doesn't find (or update) in Node - javascript

I'm trying to add user input (url's) to a database with Mongoose but also checking if the added data already exists. Instead it seems to be creating a new model every time.
This shows the res.json(input) on screen, not the test json
var mongo = require('mongodb');
var mongoose = require('mongoose');
var cors = require('cors');
// Basic Configuration
var port = process.env.PORT || 3000;
process.env.DB_URI="omg it's my secret url"
// Connect to DB.
mongoose.connect(process.env.DB_URI, { useNewUrlParser: true, useUnifiedTopology: true }).
catch(error => handleError(error));
mongoose.connection.on('error', err => {
console.log(err);
});
app.use(cors());
//First we create a schema and Model in MongoDB.
const urlSchema = new mongoose.Schema({
original_url: String,
short_url: Number
});
const urlModel = mongoose.model('urlModel', urlSchema);
app.post(/PATH, function(req, res, done) {
//Now we check if the input-url was actually valid.
if(!/(^https:\/\/)/.test(req.body.url)){
res.json({error: "Invalid url"});
}
//If it's valid we will start creating a model in the DB.
else {
var userUrl = req.body.url;
var urlNumber = 0;
var input = new urlModel({
original_url: userUrl
});
//Now we check if the url was already added to the database and return a new or updated item.
urlSchema.pre('save', function (next) {
var query = {'original_url': userUrl };
urlModel.findOneAndUpdate(query, userUrl, {upsert: true}, function(err, doc) {
if (err) return res.send(500, {error: err});
res.json({'test': 'test'});
});
});
res.json(input);
urlNumber++;
input.save(function(err, data) {
if (err) return console.log(err);
done(null, data);
});
};
});
As you can see I also have a problem updating the number that I want to attach to the url in the database, since I re-assign 0 to urlNumber at the beginning of the call. Must be because it's late but I can't seem to find a way to update that number correctly. I tried two variables and checking them against each other and an if(urlNumber > 0), but that's a problem I'm still solving. Seems so simple..
Anyway the question here is: why doesn't findOneAndUpdate seem to update? The returned res.json(input) shows a new '_id' every time, which tells me it does actually create new models every run.
Example returns
#1
{
_id: eksdjh3948wryeids293ren12;
original_url: "https://www.correct.com"
short_url: 0
}
#2
{
_id: eksdjh3948wryeids293ren13; // (<----- NEW NUMBER)
original_url: "https://www.correct.com"
short_url: 0
}

Your pre('save' function(next) {}) hook isn't calling next()

Turns out the .pre hook doesn't work with findOneAndUpdate! If I just remove that, the code now works as expected.

Related

Get data from MongoDB query ( in Auth0 rules)

I can´t get data from my mongoDb (it´s hosted in Mlab), always returns the same error: user does not exist.
It´s a very simple query but I don´t seem to be able to get it right. My user has an email and a role in the database, so I´m trying to query based on the email and get the role.
mongo('mongodb://user:pass#data.mlab.com:port/database',
function (db) {
console.log(user.email);
var users = db.collection('users');
var result = users.findOne({email: 'email#email.com'});
var role = result.role;
What am I missing?
mongoose.Promise = global.Promise; // Promise fix if you will use mongoose
mongoose
.connect(
"mongodb://user:pass#data.mlab.com:port/database",
{ useNewUrlParser: true, useMongoClient: true }
)
.then(db => {
let dbo = db.db("database");
let query = { email: "email#email.com" };
dbo
.collection("users")
.find(query)
.toArray(function(err, result) {
if (err) throw err;
// remember it returs array
// you can map this array
// like this -> result.map(user => console.log(user.role))
console.log(result);
db.close();
});
})
.catch(console.log);
try to use mongoose and make sure you use your own promises as described here
Try this:
mongo('mongodb://user:pass#data.mlab.com:port/database', function (db) {
var users = db.collection('users');
users.findOne({email: 'email#email.com'}, , function (err, user) {
if (err) return callback(err);
if (!user) return if (!user) return callback(new WrongUsernameOrPasswordError(email)
var role = result.role;
});
You can also look into the MongoDB login template used in Custom Database connections scripts: https://auth0.com/docs/connections/database/custom-db/templates/login#mongodb

node js and mongodb asynchronous issue

i'm new to mongodb and i'm having problems updating a local variable after a query. i'm using node js and i have a local variable i'm trying to update depending on my query result, but it seems that my functions returns before the query. i understand node js is asynchronous but i'm having trouble dealing with that. you can see my code below:
function userExist(userList, username){
//var usert = new UserSchema()
var exist = false
UserSchema.findOne({userName: username}, function (err, usert) {
if (err) return handleError(err);
if (usert) {
// doc may be null if no document matched
exist = true
}
})
console.log("boolean " + bool)
return exist
// return username in userList
// return query
}
I'm also having a different but unrelated issue where i'm trying to extract a specific value from a query result. my schema is as follow:
//import dependency
var mongoose = require('mongoose')
var Schema = mongoose.Schema
//create new instance of the mongoose.schema. the schema takes an
//object that shows the shape of your database entries.
var UserSchema = new Schema({
userName: String,
userID: String,
Conversations: [
{
conversationID: String,
messages: [
{
message: String,
messageID: String,
sender: String,
time: String
}
]
}
]
})
//export our module to use in server.js
module.exports = mongoose.model('User', UserSchema)
i'm trying to get the values in conversations array, add a new conversation to it and push it back in the database.
An answer to either question would be really helpful and appreciated.
just for clarification this is where i'm using the userExist function:
//Verify Username
socket.on(VERIFY_USER, (nickname, callback)=>{
if(userExist(connectedUsers, nickname)){
console.log("user exist")
callback({ userExist:true, user:null })
}else{
console.log("user does not exist")
callback({ userExist:false, user:createUser({name:nickname, socketId:socket.id})})
}
})
As already pointed out the findOne returns a promise.
You can handle the promise executing callbacks on the success or fail of the findOne result
Define two functions to pass as callbacks
function success(user){
//no error check
//doc has been found
//do something
} ;
function fail(err){
console. log(err)
}
Then in the findOne function body
if (err) return fail(err) ;
//else
return success(user)
OR
you can wrap the userExist function body to return a promise
function userExist(userList, username){
return new Promise(function(resolve, reject){
var exist = false
UserSchema.findOne({userName: username}, function (err, usert) {
if (err) return reject(err);
if (usert) {
// doc may be null if no document matched
exist = true
resolve(exist)
}
})
})
}
And when you call the userExist
userExist(userList, username).then(function(user){
//do something with the user
}).catch(function(reason) {
console.error(reason);
});

Mongoose update subdocument with save is not working

Trying to update an object in an array.
My code:
module.exports = (req, res) => {
var givenProject = req.body;
var query = mongoose.model('cv').findOne({alias: req.params.alias});
query.exec(function(err, cv){
if(err){
res.status(400).send({message: 'Could not find cv with alias: ' + req.params.alias, err: err})
}
var doc = cv.projects.id(req.params.id);
doc.langTitles = givenProject.langTitles;
doc.langDescriptions = givenProject.langDescriptions;
doc.save(function(err){
if(err){
res.status(400).send({message: 'Could not update project', err: err});
return;
}
res.status(200).send();
});
});
};
No error is given. var doc is found and the posted data have the same data structure as doc and it differs from the original.
The doc is not updated. What am I missing here?
According to the Mongoose docs:
Sub-documents enjoy all the same features as normal documents. The
only difference is that they are not saved individually, they are
saved whenever their top-level parent document is saved.
Therefore try replacing
doc.save(function(err) ...
with
cv.save(function(err) ...

NodeJS is asynchronous and my code doesn't run in the order I am expecting

postRegistrationHandler: function (account, req, res, next) {
console.log('postRegistrationHandler activated');
account.getCustomData(function(err, data) {
if (err) {
console.log(err.toString, "error string");
return next(err);
} else {
data.mongo_id = userCreationCtrl(account);
data.save();
next();
}
});
},
This function almost works properly, but the line:
data.save();
runs before the previous line finishes which means that the data I want to save isn't present at the appropriate time.
data.mongo_id = userCreationCtrl(account);
This line calls a function that creates a mongoDB document with information in the account object and then returns the _id (which is what I am trying to save.
I thought maybe using a .then() would help but that seems to be unavailable here for some reason. If anyone sees something I'm missing, that would be quite helpful. Thank you!
Here is the userCreationCtrl file as requested:
var UserSchema = require('./../models/UserModel.js');
var createNewUser = function (account, res, next){
// We will return mongoId after it is created by submitting a newUser
var mongoId = "";
// Save StormpathID (last 22 characters of account.href property)
var newStormpathId = account.href.slice(account.href.length - 22);
console.log('stormpath ID:', newStormpathId, 'just registered!');
console.log(account);
// Create new user from model by recycling info from the Stormpath registration form and include the stormpathId as well.
var newUser = new UserSchema({
stormpathId: newStormpathId,
firstName: account.givenName,
lastName: account.surname,
email: account.email,
street: account.street,
city: account.city,
zip: account.zip
});
// This saves the user we just created in MongoDB
newUser.save(function(err, result){
console.log(result);
if (err) {
console.error(err);
}
else {
console.log("User created in MongoDB, attempting to return mongoDB _id to stormpath customData");
// Keep track of the new user's mongo _id so we can return it to the previous function and save it as Stormpath custom data.
mongoId = result._id;
console.log(mongoId, "mongoid");
return result._id;
}
});
};
module.exports = createNewUser;
You have userCreationCtrl expecting 3 arguments, account, res, and next. next is the callback that should be called after the user is created so instead of return result._id you should call next like so:
// inside of createNewUser()
newUser.save(function(err, result){
console.log(result);
if (err) {
console.error(err);
}
else {
console.log("User created in MongoDB, attempting to return mongoDB _id to stormpath customData");
// Keep track of the new user's mongo _id so we can return it to the previous function and save it as Stormpath custom data.
mongoId = result._id;
console.log(mongoId, "mongoid");
// IMPORTANT change to make it all work...
// get rid of return result._id because its not doing anything
// pass the value to your callback function instead of returning the value
next(null, result._id);
}
});
then calling code in postRegistrationHandler should look like this:
account.getCustomData(function(err, data) {
if (err) {
console.log(err.toString, "error string");
return next(err);
} else {
// pass in a callback as the 3rd parameter that will be called by newUser.save() when its finished
userCreationCtrl(account, null, function(err, resultId) {
data.save();
next();
});
}
});

Mongoose - Create document if not exists, otherwise, update- return document in either case

I'm looking for a way to refactor part of my code to be shorter and simpler, but I don't know Mongoose very well and I'm not sure how to proceed.
I am trying to check a collection for the existence of a document and, if it doesn't exist, create it. If it does exist, I need to update it. In either case I need to access the document's contents afterward.
What I've managed to do so far is query the collection for a specific document and, if it's not found, create a new document. If it is found, I update it (currently using dates as dummy data for this). From there I can access either the found document from my initial find operation or the newly saved document and this works, but there must be a better way to accomplish what I'm after.
Here's my working code, sans distracting extras.
var query = Model.find({
/* query */
}).lean().limit(1);
// Find the document
query.exec(function(error, result) {
if (error) { throw error; }
// If the document doesn't exist
if (!result.length) {
// Create a new one
var model = new Model(); //use the defaults in the schema
model.save(function(error) {
if (error) { throw error; }
// do something with the document here
});
}
// If the document does exist
else {
// Update it
var query = { /* query */ },
update = {},
options = {};
Model.update(query, update, options, function(error) {
if (error) { throw error; }
// do the same something with the document here
// in this case, using result[0] from the topmost query
});
}
});
I've looked into findOneAndUpdate and other related methods but I'm not sure if they fit my use case or if I understand how to use them correctly. Can anyone point me in the right direction?
(Probably) Related questions:
How to check if that data already exist in the database during update (Mongoose And Express)
Mongoose.js: how to implement create or update?
NodeJS + Mongo: Insert if not exists, otherwise - update
Return updated collection with Mongoose
Edit
I didn't come across the question pointed out to me in my searching, but after reviewing the answers there I've come up with this. It's certainly prettier, in my opinion, and it works, so unless I'm doing something horribly wrong I think my question can probably be closed.
I would appreciate any additional input on my solution.
// Setup stuff
var query = { /* query */ },
update = { expire: new Date() },
options = { upsert: true };
// Find the document
Model.findOneAndUpdate(query, update, options, function(error, result) {
if (!error) {
// If the document doesn't exist
if (!result) {
// Create it
result = new Model();
}
// Save the document
result.save(function(error) {
if (!error) {
// Do something with the document
} else {
throw error;
}
});
}
});
You are looking for the new option parameter. The new option returns the newly created document(if a new document is created). Use it like this:
var query = {},
update = { expire: new Date() },
options = { upsert: true, new: true, setDefaultsOnInsert: true };
// Find the document
Model.findOneAndUpdate(query, update, options, function(error, result) {
if (error) return;
// do something with the document
});
Since upsert creates a document if not finds a document, you don't need to create another one manually.
Since you wish to refactor parts of your code to be shorter and simpler,
Use async / await
Use .findOneAndUpdate() as suggested in this answer
let query = { /* query */ };
let update = {expire: new Date()};
let options = {upsert: true, new: true, setDefaultsOnInsert: true};
let model = await Model.findOneAndUpdate(query, update, options);
///This is simple example explaining findByIDAndUpdate from my code added with try catch block to catch errors
try{
const options = {
upsert: true,
new: true,
setDefaultsOnInsert: true
};
const query = {
$set: {
description: req.body.description,
title: req.body.title
}
};
const survey = await Survey.findByIdAndUpdate(
req.params.id,
query,
options
).populate("questions");
}catch(e){
console.log(e)
}
Here is an example I am using. I have to return custom responses for UI updates etc. This can be even shorter. User is
const UserScheme = mongoose.Schema({
_id: String,
name: String,
city: String,
address: String,
},{timestamps: true});
const User = mongoose.model('Users', UserScheme);
async function userUpdateAdd(data){
var resp = '{"status": "error"}';
if(data){
var resp = await User.updateOne({ _id: data._id }, data).then(function(err, res){
console.log("database.userUpdateAdd -> Update data saved in database!");
if(err){
var errMessage = err.matchedCount == 0 ? "User Record does not exist, will create new..." : "Record not updated";
// If no match, create new
if(err.matchedCount == 0){
const create_user = new User(data);
resp = create_user.save().then(function(){
console.log("database.userUpdateAdd -> Data saved to database!");
return '{"status":"success", "message": "New User added successfully"}';
});
return resp;
}
// Exists, return success update message
if(err.matchedCount == 1){
return '{"status": "success", "message" : "Update saved successfully"}';
} else {
return '{"status": "error", "code": "' + err.modifiedCount + '", "message": "' + errMessage + '"}';
}
}
})
.catch((error) => {
//When there are errors We handle them here
console.log("database.userUpdateAdd -> Error, data not saved! Server error");
return '{"status": "error", "code": "400", "message": "Server error!"}';
});
}
return resp;
}
Here's an example:
const mongoose = require('mongoose');
mongoose.connect('mongodb://localhost/rsvp', {useNewUrlParser: true, useUnifiedTopology: true});
const db = mongoose.connection;
db.on('error', () => {
console.log('mongoose connection error');
});
db.once('open', () => {
console.log('mongoose connected successfully');
});
const rsvpSchema = mongoose.Schema({
firstName: String,
lastName: String,
email: String,
guests: Number
});
const Rsvp = mongoose.model('Rsvp', rsvpSchema);
// This is the part you will need... In this example, if first and last name match, update email and guest number. Otherwise, create a new document. The key is to learn to put "upsert" as the "options" for the argument.
const findRsvpAndUpdate = (result, callback) => {
Rsvp.findOneAndUpdate({firstName: result.firstName, lastName: result.lastName}, result, { upsert: true }, (err, results) => {
if (err) {
callback(err);
} else {
callback(null, results);
}
})
};
// From your server index.js file, call this...
app.post('/rsvps', (req, res) => {
findRsvpAndUpdate(req.body, (error, result) => {
if (error) {
res.status(500).send(error);
} else {
res.status(200).send(result);
}
})
});

Categories

Resources