Mongoose update through $set a value if NOT undefined (NodeJS) - node.js

what's the proper way to check for undefined values? What I want to do is to have a PUT method that will update those fields that are not empty. For example, if I send req.body.name = 'John' and no req.body.job I want my request to only change the name.
Some code:
router.put('/:id', (req, res) => {
const query = {_id: req.params.id};
const update = {
$set: {
name: req.body.name,
job: req.body.job
}
};
User.findOneAndUpdate(query, update,
(err, userUpdated) => {
if (err) {
console.log('Error while updating');
console.log(err);
} else {
res.send(userUpdated);
}
});
});
This will of course throw an error:
CastError: Cast to number failed for value "undefined" at path "job"
Now I can manually check if req.body.job is empty and if it is set it's value to the value the user had previously, but that seems like a hack, not elegant and a lot of writing for each route.
I have checked the docs but none of the options provided there seem to do the job. I also came across something like express validator but this will probably just do a return if the value is empty. Another options would be to simply send the value from the front-end part.
I'm new to backend development and I'm not sure if I'm doing stuff the "right way". So please, any comment on how it should be done would be nice (also if my code looks odd, feel free to guide me :)), thanks!

You can write your own method to do this.
For example this example
var req = {body: {name: undefined, job: 'yes'}};
const _ = require('lodash');
const out = {};
_(req.body).forEach((value,key) => {
if (!_.isEmpty(value)){
out[key] = value;
}
});
console.log(out);
Is having this output
{ job: 'yes' }
You can also write it as middleware, if you want, if you write it as this
function onlyNotEmpty(req, res, next) => {
const out = {};
_(req.body).forEach((value, key) => {
if (!_.isEmpty(value)) {
out[key] = value;
}
});
req.bodyNotEmpty = out;
next();
}
Then you can write your method with middleware
router.put('/:id', onlyNotEmpty, (req, res) => {
const query = {_id: req.params.id};
const update = {
$set: req.bodyNotEmpty
};
// This will be the same
});

Related

Is there a better way to implement PUT method in Express than explicitly assigning values?

Is there a better way to implement the PUT method in Express than explicitly assigning each value from the request to a db loaded object (from MongoDB)?
function put(req, res) {
const { school } = req;
school.name = req.body.name;
school.nature = req.body.nature;
school.website = req.body.website;
school.facebookURL = req.body.facebookURL;
school.instagramURL = req.body.instagramURL;
...
req.school.save((err) => {
if (err) {
return res.send(err);
}
return res.json(school);
});
}
My document has quite a lot of attributes and the JSON will get quite complex. I am relatively new to Express and would like to know if there is a trick to do this more optimally.
Assuming the loaded object(school) is a mongodb collection.
You can use the update method of the collection without explicitly specifying the data to update.
function put(req, res) {
const { school } = req;
school.update({
_id:ObjectId(req.param.id)
},
req.body)
.then(function (success) {
res.json();
})
.catch(function (error) {
res.status(404).send(err);
}); }
ps: on mobile device right now, so formatting might be poor.
Following the hint given to me by #LawrenceCherone in the comments, I came up with a pretty elegant solution to the problem. I am doing the validation in the model schema, so the code here can be short and sweet!
function put(req, res) {
const { school } = req;
Object.assign(school, req.body);
req.school.save((err) => {
if (err) {
return res.send(err);
}
return res.json(school);
});
}

Projection in .find() not working for MongoDB/Express [duplicate]

This question already has answers here:
batchSize field name ignored in Field Projection
(2 answers)
Closed 4 years ago.
I have an express app that's supposed to query my MongoDB database, then return only certain fields of the result. My code for this currently looks like this:
app.get('/stored', (req, res) => {
let pollId = req.query.id;
let pollCursor = db.collection(collName).find({"_id": ObjectId(pollId)}, {"_id": false, "pollName": false}).toArray((err, data) => {
if(err) return err;
let dataObj = data[0];
console.log(dataObj);
});
});
I expect to recieve a data object that contains all fields except the '_id' and 'pollName' fields. However, the object I get still contains both these fields, as well as all the other fields. What am I doing wrong?
The first you have to test like this:
app.get('/stored', (req, res) => {
let pollId = req.query.id;
res.send(pollId)
return;
});
if pollId match with pollId from client send, it's fine. You should create a query to find like this:
let query = {_id: pollId}
This is my code, hope help you
// https://tudiendanhngon.herokuapp.com/author/detail?id=5adc963c7b76157c65d3b1d9
app.get("/author/detail", function (req, res) {
var id = req.query.id;
var query = {_id: id};
db.collection(COLLECTION_AUTHORS).find(query).exec(function (err, author) {
if (err) {
res.send("Error")
} else {
res.json(author);
}
});
});

Express/Mongoose use object function as callback doesn't work

I want to call function from my object in express route. This function should call mongoose query, then run next, next etc. - all needed operations.
Here is my example route:
var MailSender = require('../../libs/mailer');
router.get('/mailer/test', function (req, res, next) {
MailSender.getPending();
});
And mailer file:
(here include all required)
module.exports = {
currentMails : {},
getPending : function() {
MailObj.find()
.limit(10)
.exec(this.blockPending);
},
blockPending : function(err, mail) {
currentMails = {};
mail.forEach(function(data) {
let mailId = mongoose.Types.ObjectId(data._id);
currentMails[mailId] = data;
});
MailObj.update({ _id: { $in: Object.keys(currentMails) } }, { block: 1 }, {multi: true}, function() {
// Doesn't work
this.myNextFunc();
});
},
myNextFunc : function() {
console.log("Yep!");
}
}
getPending - it works great and call blackPending with query results.
blockPending - works greats, I can prepare ids and update records
But... myNextFunc() doesn't work and I can't call any object function from this scope (console says that they are undefined). I know, that I make something wrong but... what?
I'ld like to encapsule related functions in such objects and run inside as callbacks. What am I doing wrong?
As far as you are using Mongoose, why don't you take profit of it, and you update each mail in the loop?? It is less efficient, but maybe as a first approach it deserves:
var numUpdates = 0;
mail.forEach(function(data) {
data.block = 1;
data.save(function(err, mailSaved) {
//check error
if(numUpdates ++ >= mail.length) {
this.myNextFunc();
}
})
});

Express sends an empty object, but a console.log displays the object correctly [duplicate]

This question already has answers here:
How do I return the response from an asynchronous call?
(41 answers)
Closed 6 years ago.
I'm implementing a simple local auth with express and mongoDB(using mongoose) and for some reason the server sends back an empty response object for user, but the token gets sent in the response. I included a console.log statement immediately before the return to try and debug it a little bit, and the object logged there is the correct one with all of the data. This is what the create code looks like
import mongoose from 'mongoose';
import jwt from 'jsonwebtoken';
import json from '../helpers/json';
var User = mongoose.model('User');
module.exports = function() {
var obj = {};
obj.create = function (req, res) {
var roles = ['authenticated'];
User.count({}, (err, len) => {
if (!len) {
roles.push('admin');
}
var user = new User(req.body);
user.roles = roles;
user.provider = 'local';
user.token = jwt.sign(user, global.config.secret, {expiresIn: 10800});
user.save((err, user) => {
if (err) {
return json.bad(err, res);
}
json.good({
record: user,
token: user.token
}, res);
});
});
};
return obj;
};
Like I said, I had included a console.log statement and the user will display properly.
If you are wondering, the json.good is a helper function that I wrote that basically looks like this
module.exports = {
good: function (obj, res) {
res.send({
success: 1,
res: obj
});
},
bad: function (err, res) {
var obj = {
success: 0,
res: err
};
if (obj.res.errors) {
obj.res.messages = [];
for (var i in obj.res.errors) {
obj.res.messages.push(obj.res.errors[i].message;
}
obj.res.messages = obj.res.messages[0];
}
res.send(obj);
}
};
I am also allowing the correct headers and methods in my express file. This code is identical to code that I have used before, but I am missing something it seems.
Any help would be appreciated, thank you!
!!!!!!! FIXED FIXED FIXED !!!!
I figured out the problem, it was in my model. I had
UserSchema.methods = {
toJSON: function() {
var obj = this.toObject();
delete obj.password;
delete obj.following;
}
};
I had forgotten to return the obj at the end. Thanks everyone!
Make sure that the Value Type in MongoDB matches the Variable type...
So if you have a Key named 'fav' that is an Int32, Then make sure that the variable you use to find it is an Int32.

Express Router param validation

The express 4x api docs claim that you can pass regex as a second argument to router.param in order validate params.
The method could now be used to effectively validate parameters (and
optionally parse them to provide capture groups)
It then provides the following examples.
// validation rule for id: should be one or more digits
router.param('id', /^\d+$/);
router.get('/user/:id', function(req, res) {
res.send('user ' + req.params.id);
});
// validation rule for range: should start with one more alphanumeric characters, followed by two dots, and end with one more alphanumeric characters
router.param('range', /^(\w+)\.\.(\w+)?$/);
router.get('/range/:range', function(req, res) {
var range = req.params.range;
res.send('from ' + range[1] + ' to ' + range[2]);
});
But, this doesn't actually seem to work. Taking a deeper dive, it doesn't look as if the express code actually supports what the docs claim. In fact, passing anything other than a function will get you a tidy invalid param() call exception.
Using
express 4.12.3
node 0.12.4
So my question is whether this functionality actually exists or if I'm doing something wrong. I'm trying to accomplish the same thing provided in the doc but am receiving the error mentioned above. Any guidance would be greatly appreciated :)
The answer can be found here.
Essentially the following snippet would need to be run prior to leveraging the router.param(fn) method as outlined above if you're using express <= 4.11.
express 4.11
router.param(function(name, fn) {
if (fn instanceof RegExp) {
return function(req, res, next, val) {
var captures;
if (captures = fn.exec(String(val))) {
req.params[name] = captures;
next();
} else {
next('route');
}
}
}
});
express 4.12
If you're using express >= 4.12 you can accomplish the same without the need of router.param(fn) using the following. In fact, the pre 4.12 example above will pop a deprecation warning.
app.get('/user/:userId([0-9]+)', fn);
While this is stated in the doc, it isn't quite clear.Hope this helps.
I was facing the same issue and I got it resolved by converting my code
exports.customerValidator = () => {
return [
body("name").trim().toString(),
body("email")
.isEmail()
.withMessage("Please enter a valid email id")
.custom((value, { req }) => {
return Customer.findOne({ email: value })
.then((customer) => {
if (customer) {
return Promise.reject("E-mail address is already exists!");
}
})
})
.normalizeEmail(),
body("address").trim().toString(),
body("city").trim().toString()
]
}
to this:
exports.customerValidator = () => {
return [
body('name').trim(),
body("email")
.isEmail()
.normalizeEmail()
.custom((value, { req }) => {
return Customer.find({ email: value })
.then((customer) => {
if (customer) {
return Promise.reject("E-mail address is already exists!");
}
})
}),
body("address").trim(),
body("city").trim(),
];
};

Resources