Query parsing in mongodb - node.js

I am passing a query parameter which is look like this
var str="["56557e9e8091afc81a4371c8","565ac01b076fd1141492b594","565ab985076fd1141492b586","565aba0a076fd1141492b58c","565ab9d3076fd1141492b588","565aba4b076fd1141492b58e"]";
and it is being received into my node api which is look like this
console.log(req.query.pageModalIds);
now I want parse my query like this
return RuleSetModel.find({ 'pageId': { $in: req.query.pageModalIds } }, function (err, ruleset) {
if (!err) {
console.log('get rule set');
return res.send(ruleset);
} else {
res.statusCode = 500;
console.log('Internal error(%d): %s', res.statusCode, err.message);
return res.send({ error: 'Server error' });
}
});
This method does not provide my data. what is my fault. Thanks.
N.B This is working If i set as fixed parameter which is below..
return RuleSetModel.find({ 'pageId': { $in: ["565a9fddf84313fc08b260dc","565ac01b076fd1141492b594"] } }, function (err, ruleset) {

I think the problem lies with req.query.pageModalIds. Since you are not doing any processing to that it likely stays as a String Object. You need to convert it into array object before applying to query.A sample is shown below,
var pageModalIds = JSON.parse(req.query.pageModalIds)
After this you can apply $in query on this new variable. Try this and let me know. :)

Related

Return 404 code in proper way instead empty array

I have an quite simple application the idea is that someone has unique code which value are stored in one mongo collection in other we are keeping some data which we need to return if the key was found in first collection.
As probably you have noticed I'm using NodeJS with MongoDB and Mongoose, Express.
I have a problem with method bellow:
exports.getCompanyByKey = function(req, res) {
console.log(req.params.keyvalue);
var query = Company.where({keyValue : req.params.keyvalue});
query.findOne(function(err, company){
if(err){
res.send(err);
}else{
SampleData.findOne({}, function(err, sample_data){
if(err)
res.send(err);
res.json(sample_data);
});
}
});
};
The problem is that it will always return the data beause it's not throwing an error but empty array - so is there any other good and proper way as it should be don to throw 404 error without statement such as if(length<0) res.status(404).send('Error message).
I simply want to minimalize amount of if statements.
Maybe there is some other way to write implementation od error handling for mongoose which in general instead returning empty array will give us error code with message?
It's not exactly clear what you're asking, but if you want to make an error condition out of something that is not normally an error, then an if statement (or some other test like that) is required to test for that specific condition.
You could make your own function for querying that turns an empty response into an error and you could "hide" the if condition in that function if you want, but it's still an if condition that tests for your specific condition.
So, to return a 404 if the array is empty, you would just add an if statement (as you already appear to know):
exports.getCompanyByKey = function(req, res) {
console.log(req.params.keyvalue);
var query = Company.where({keyValue : req.params.keyvalue});
query.findOne(function(err, company){
if(err){
res.status(500).send(err);
} else {
SampleData.findOne({}, function(err, sample_data){
if(err) {
res.status(500).send(err);
} else {
if (sample_data.length) {
res.json(sample_data);
} else {
res.status(404).send("no data");
}
}
});
}
});
};
FYI, you also need to make sure you are properly setting a status code when there's an error and that you are never sending multiple responses to the same request (even when there's an error). I've also fixed several cases of those issues in your code.
This could likely be written cleaner and responses consolidated by using the promise interface to your database and send an error in one .catch().
For example, you could simplify your code by creating a utility function for .findOne() that detects and sends an error response automatically:
function findOne(res, db, q, cb) {
db.findOne(q, function(err, data) {
if (err) {
res.status(500).send(err);
cb(err);
} else if (!q.length) {
res.status(404).send("no data");
cb(new Error("no data"));
} else {
cb(null, data);
}
});
}
Then, your function could be simplified to this:
exports.getCompanyByKey = function(req, res) {
var query = Company.where({keyValue : req.params.keyvalue});
query.findOne(function(err, company){
if(err){
res.status(500).send(err);
} else {
findOne(res, SampleData, {}, function(err, sample_data) {
// any error response has already been sent
if (!err) {
res.json(sample_data);
}
});
}
});
};
Again, this would be better to use your Db's promise interface.

unable to add property to the json object

I am trying to add status to a response on successful update but I am not able to add the status property to json object of form. Here is my code
apiRouter.post('/forms/update', function(req, res){
if(req.body.id !== 'undefined' && req.body.id){
var condition = {'_id':req.body.id};
Form.findOneAndUpdate(condition, req.body, {upsert:true}, function(err, form){
if (err) return res.send(500, { error: err });
var objForm = form;
objForm.status = "saved successfully";
return res.send(objForm);
});
}else{
res.send("Requires form id");
}
});
and here is the response that I get, notice status is missing
{
"_id": "5580ab2045d6866f0e95da5f",
"test": "myname",
"data": "{\"name\":3321112,\"sdfsd\"344}",
"__v": 0,
"id": "5580ab2045d6866f0e95da5f"
}
I am not sure what I am missing.
Try to .toObject() the form:
Form.findOneAndUpdate(condition, req.body, {upsert:true}, function(err, form){
if (err) return res.send(500, { error: err });
var objForm = form.toObject();
objForm.status = "saved successfully";
return res.send(objForm);
});
Mongoose query result are not extensible (object are frozen or sealed), so you can't add more properties. To avoid that, you need to create a copy of the object and manipulate it:
var objectForm = Object.create(form);
objectForm.status = 'ok';
Update: My answer is old and worked fine, but i will put the same using ES6 syntax
const objectForm = Object.create({}, form, { status: 'ok' });
Another way using spread operator:
const objectForm = { ...form, status: 'ok' }
Try changing res.send(objForm) to res.send(JSON.stringify(objForm)). My suspicion is that the the Mongoose model has a custom toJson function so that when you are returning it, it is transforming the response in some way.
Hopefully the above helps.
Create empty object and add all properties to it:
const data = {};
data._id = yourObject._id; // etc
data.status = "whatever";
return res.send(data);
Just create a container.
array = {};
Model.findOneAndUpdate(condition, function(err, docs){
array = docs;
array[0].someField ="Other";
});

New to NodeJS. Mongoose How to Async multiple queries?

Ok, lets say I have two Models. Contract and CommLog. Both work find independently but I need many CommLog to relate to each Contract.
In the ContractSchema trying async
ContractSchema.methods.getCommLog = function getCommLog(){
var log = false;
async.parallel([
function(){
CommLog.find({commType:'contract',parent:this._id},function(err,comms){
log = comms;
});
}],
function(){return log;});
};
Where I am trying to use it
router.get('/:code', function(req, res, next) {
Contract.findOne({accessCode:req.params.code},function(err,contract){
if(err)
res.send(err);
var data;
if(contract != null){
var comms = contract.getCommLog();
data = {error:false,data:contract,commlog:comms}
}else{
data = {error:true,message:"No Contract"}
}
res.json(data);
});
});
Where it shows var comms = contract.getCommLog(); It is never returning anything because the getCommLog() is not executing async...
I think its my misunderstanding of mongoose querying, so if you understand what I am trying to accomplish, please let me know what I am doing wrong. I have tried without async which would always return false.
The find call can return all matching results with one query, so I don't think you need async here. The reason it is not populating correctly when you call res.json(data) is because you are not waiting for the method call to finish before you fire off your server response. You would be better off nesting an additional CommLogs.find call within the Contract.find call, and only sending your response once that finishes.
//pseudo code:
Contract.find({}, function(err, contract) {
if(err || !contract) {
//return error response
}
else {
CommLogs.find({contract: contract._id}, function(err, commlogs) {
if(err || !commlogs) {
//return error response 2
}
else {
res.json({errors: false, contract: contract, commlogs: commlogs});
}
});
}
}

mongoose: detect if document inserted is a duplicate and if so, return the existing document

This is my code:
var thisValue = new models.Value({
id:id,
title:title //this is a unique value
});
console.log(thisValue);
thisValue.save(function(err, product, numberAffected) {
if (err) {
if (err.code === 11000) { //error for dupes
console.error('Duplicate blocked!');
models.Value.find({title:title}, function(err, docs)
{
callback(docs) //this is ugly
});
}
return;
}
console.log('Value saved:', product);
if (callback) {
callback(product);
}
});
If I detect that a duplicate is trying to be inserted, i block it. However, when that happens, i want to return the existing document. As you can see I have implemented a string of callbacks, but this is ugly and its unpredictable (ie. how do i know which callback will be called? How do i pass in the right one?). Does anyone know how to solve this problem? Any help appreciated.
While your code doesn't handle a few error cases, and uses the wrong find function, the general flow is typical giving the work you want to do.
If there are errors other than the duplicate, the callback isn't called, which likely will cause downstream issues in your NodeJs application
use findOne rather than find as there will be only one result given the key is unique. Otherwise, it will return an array.
If your callback expected the traditional error as the first argument, you could directly pass the callback to the findOne function rather than introducing an anonymous function.
You also might want to look at findOneAndUpdate eventually, depending on what your final schema and logic will be.
As mentioned, you might be able to use findOneAndUpdate, but with additional cost.
function save(id, title, callback) {
Value.findOneAndUpdate(
{id: id, title: title}, /* query */
{id: id, title: title}, /* update */
{ upsert: true}, /* create if it doesn't exist */
callback);
}
There's still a callback of course, but it will write the data again if the duplicate is found. Whether that's an issue is really dependent on use cases.
I've done a little clean-up of your code... but it's really quite simple and the callback should be clear. The callback to the function always receives either the newly saved document or the one that was matched as a duplicate. It's the responsibility of the function calling saveNewValue to check for an error and properly handle it. You'll see how I've also made certain that the callback is called regardless of type of error and is always called with the result in a consistent way.
function saveNewValue(id, title, callback) {
if (!callback) { throw new Error("callback required"); }
var thisValue = new models.Value({
id:id,
title:title //this is a unique value
});
thisValue.save(function(err, product) {
if (err) {
if (err.code === 11000) { //error for dupes
return models.Value.findOne({title:title}, callback);
}
}
callback(err, product);
});
}
Alternatively, you could use the promise pattern. This example is using when.js.
var when = require('when');
function saveNewValue(id, title) {
var deferred = when.defer();
var thisValue = new models.Value({
id:id,
title:title //this is a unique value
});
thisValue.save(function(err, product) {
if (err) {
if (err.code === 11000) { //error for dupes
return models.Value.findOne({title:title}, function(err, val) {
if (err) {
return deferred.reject(err);
}
return deferred.resolve(val);
});
}
return deferred.reject(err);
}
return deferred.resolve(product);
});
return deferred.promise;
}
saveNewValue('123', 'my title').then(function(doc) {
// success
}, function(err) {
// failure
});
I really like WiredPrairie's answer, but his promise implementation is way too complicated.
So, I decided to add my own promise implementation.
Mongoose 3.8.x
If you're using latest Mongoose 3.8.x then there is no need to use any other promise module, because since 3.8.0 model .create() method returns a promise:
function saveNewValue(id, title) {
return models.Value.create({
id:id,
title:title //this is a unique value
}).then(null, function(err) {
if (err.code === 11000) {
return models.Value.findOne({title:title}).exec()
} else {
throw err;
}
});
}
saveNewValue('123', 'my title').then(function(doc) {
// success
console.log('success', doc);
}, function(err) {
// failure
console.log('failure', err);
});
models.Value.findOne({title:title}).exec() also returns a promise, so there is no need for callbacks or any additional casting here.
And if you don't normally use promises in your code, here is callback version of it:
function saveNewValue(id, title, callback) {
models.Value.create({
id:id,
title:title //this is a unique value
}).then(null, function(err) {
if (err.code === 11000) {
return models.Value.findOne({title:title}).exec()
} else {
throw err;
}
}).onResolve(callback);
}
Previous versions of Mongoose
If you're using any Mongoose version prior to 3.8.0, then you may need some help from when module:
var when = require('when'),
nodefn = require('when/node/function');
function saveNewValue(id, title) {
var thisValue = new models.Value({
id:id,
title:title //this is a unique value
});
var promise = nodefn.call(thisValue.save.bind(thisValue));
return promise.spread(function(product, numAffected) {
return product;
}).otherwise(function(err) {
if (err.code === 11000) {
return models.Value.findOne({title:title}).exec()
} else {
throw err;
}
});
}
I'm using nodefn.call helper function to turn callback-styled .save() method into a promise. Mongoose team promised to add promises support to it in Mongoose 4.x.
Then I'm using .spread helper method to extract the first argument from .save() callback.

NodeJS + Mongoose: Updating all fields on a Mongoose model

I'm building out an api using Node, MongoDB and Mongoose. One thing that is bugging me is that you can't seem to set multiple fields at once:
app.put('/record/:id', function(req, res) {
Record.findById(req.params.id, function(err, doc) {
if (!err) {
doc.update(req.params);
doc.save();
...
However, it seems that you have to work out the update query and run it on the Model object rather than on the document object. Unless you want to assign individual properties and run save() at the end.
Is there any way of accomplishing this without having to write a Mongo query?
jsaak's answer is good but doesn't work for nested objects. I elaborated on his answer by searching and setting nested objects.
I added these functions to a utility.js file
var _ = require('underscore');
exports.updateDocument = function(doc, SchemaTarget, data) {
for (var field in SchemaTarget.schema.paths) {
if ((field !== '_id') && (field !== '__v')) {
var newValue = getObjValue(field, data);
console.log('data[' + field + '] = ' + newValue);
if (newValue !== undefined) {
setObjValue(field, doc, newValue);
}
}
}
return doc;
};
function getObjValue(field, data) {
return _.reduce(field.split("."), function(obj, f) {
if(obj) return obj[f];
}, data);
}
function setObjValue(field, data, value) {
var fieldArr = field.split('.');
return _.reduce(fieldArr, function(o, f, i) {
if(i == fieldArr.length-1) {
o[f] = value;
} else {
if(!o[f]) o[f] = {};
}
return o[f];
}, data);
}
implement as:
var util = require('./utility');
app.put('/record/:id', function(req, res) {
Record.findById(req.params.id, function(err, doc) {
if (!err) {
utils.updateDocument(doc, Record, req.params);
doc.save();
...
Maybe this has changed since this question was first asked, but you can update multiple paths in Mongoose with the set method ike:
// object
doc.set({
path : value,
path2 : {
path : value
}
});
doc.save();
References
http://mongoosejs.com/docs/api.html#document_Document-set
direct updating is not recommended according to this document:
http://mongoosejs.com/docs/2.7.x/docs/updating-documents.html
i solved it like this:
Book.findOne({isbn: req.params.isbn}, function (err, book){
if (err) {
res.send(422,'update failed');
} else {
//update fields
for (var field in Book.schema.paths) {
if ((field !== '_id') && (field !== '__v')) {
if (req.body[field] !== undefined) {
book[field] = req.body[field];
}
}
}
book.save();
}
});
If you want to update the entire document , you can delete the document based on its id and store the entire object again.
That object must contain data for each and every fields of the mongo document.
Here is an example.
mongoDBCollectionObject.findOneAndRemove({ // -- it will delete the entire document
_id: req.body.fieldsdata._id // here fiedsdata is exact copy with modification of previous data
}, function(err, data) {
var newFieldsData = new mongoDBCollectionObject(fieldsdata); //-- fieldsdata updated data
newFieldsData.save(function(err, data) { // save document to that collection with updated data
if (err) {
console.log(err);
} else
res.json({
success: true
});
});
})
To clarify the question, it looks like you are taking the Request parameters and using those to find and update the given document.
Is there any way of accomplishing this without having to write a Mongo query?
The obvious answer is to update the Model object with the value from the Request. Which is what you suggest...
Unless you want to assign individual properties and run save() at the end.
But it seems like you don't want to do this? It sounds like you want to update the Model object directly from the Request object?
You can do this if you really want. You just loop through req.params and set the doc values where appropriate.
for(var i in req.params) {
if(req.params[i] != doc[i]){
doc[i] = req.params[i];
}
}
It should be as simple as this. However, you only want to do this if you have a whole bunch of validation code on the Model objects. The whole point to the Model is that you don't want to get random data in the DB. The line above will generically "set" the correct values, but you'll definitely need to include code for authentication, authorization and validation around that simple for loop.
try to updating the collection without the find, like this
Record.update({_id:req.params.id}, {$set: { field: request.field }}, {upsert: true}, function(err{...})
The option upsert create the document if not exist.
In case you have a new object and want to update whole object in the database, you can update multiple fields at once like this:
find the object
get all schema paths (fields)
save the new object.
SomeModel.findOne({ 'id': 'yourid' },function (err, oldObject) {
if (err) return handleError(err);
// get all schema paths (fields)
SomeModel.schema.eachPath(function(path) {
// leave __id and __v alone
if (path != '_id' && path != '__v') {
// update the data from new object
oldObject[path] = newObject[path];
}
})
oldObject.save(function(err) {
if (err)
console.log(err)
});
})
A neat and clean approach would be using async await and findOneAndRemove along with create Here is the sample code
try {
let resp = await this.findOneAndRemove({ _id: req.body._id });
let entry = await this.create(req.body);
} catch (err) {
}
Don't Forget to mark this whole function as async

Resources