Updating document within find - javascript

I'm having issues updating a document from within a find using Mongoose. The issue is only when I attempt to overwrite the document with an object (e.g doc = req.body). I am however able to directly set properties of the original document to a specific string (e.g. doc.name = 'jason borne';).
I've verified that res.body is an object, so I don't see why I'm unable to set it this way.
Client.findById(req.params.client_id, function (err, client) {
if (err)
return next(new restify.InternalError(err));
// client.name = 'jason borne';
client = req.body;
client.save(function(err) {
if (err)
return next(new restify.InternalError(err));
res.send(client);
});
});
When attempting to set the doc to an object, I receive the error:
TypeError: Object # has no method 'save'
I'm aware that I can do an update with a simple Client.update(...) command, however this method does not allow my schema middleware or validation to run (which is notated in the Mongoose documentation).
Any thoughts? I'm new to Node, and Mongoose.

You need to use something like underscore's extend method to copy the properties of req.body into the client object instead of just re-pointing client to req.body as you are now.
var _ = require('underscore');
Client.findById(req.params.client_id, function (err, client) {
if (err)
return next(new restify.InternalError(err));
_.extend(client, req.body);
client.save(function(err) {
if (err)
return next(new restify.InternalError(err));
res.send(client);
});
});

The symptoms you're now getting are caused by the fact that you're replacing a mogoose model object (with methods like save, find, etc), by a simple json object parsed from your body, which is missing save.
Try doing an update instead of find/save.
Client.update({_id: req.params.client_id}, {$set : req.body}, function(err) {...});
Or try to merge your req.body to the client object.

Related

How to find the Error message in Error object send by NodeJS in Angular

The Nodejs functions return an error from try/catch scope, such as the one below if the user doesn't exist of if a database is not reachable:
router.delete('/delete/:email', async (req, res) => {
var email = req.params.email;
try {
let result = await User.remove({"email": email});
res.status(204).send(email);
} catch (err) {
res.status(400).send(err);
}
});
I can also return the Error from Nodejs server by myself:
return res.status(400).send(new Error(`The user with email ${email} doesn't exist.`));
The first problem is that I can't find the error message that is embedded somewhere deep in the body the returned Error object. It is stored in one of its 100+ attributes. Where should I look for it so I could display in on a screen for the end user to read it?
Then, the err object generated by the try/catch scope has a set of different attributes comparing to the Error object created with new Error("Here is my error message"). Is there a way to normalize the returned Errors so they all have the same or similar attributes?
You don't need to return the whole error object from the server, and arguably shouldn't since error messages can expose internals about your code and infrastructure.
One way you could handle this is to format and return an error message from the server yourself. Assuming you're using express this would look something like:
return res.status(400).json({ message: `The user with email ${email} doesn't exist.` });
Alternatively you could use an error handling middleware like strong-error-handler found here: https://github.com/strongloop/strong-error-handler which automatically builds a json formatted message that's easier to parse, but keep in mind that the content of the message differs depending on whether you set debug mode to true or no.
If you want to develop a secure web application with nice error handling, i will suggest you the following structure.
Step 1. At front end divide your api calls in four main operations for e.g. inset,update,query and filter.
now whenever your page loads and you want to show some data fetched from server then your api call must be like 'https://domainname.tld/server/query' and send some payload with this api call according to need of your data requirements to be fetched.
At backend probably at Server.js handle like this :
app.all("/server/query", function (req, res) {
try {
console.log(a);
// some database or io blocking process
} catch (error) {
// error handling
var err = writeCustomError(error.message || error.errmsg || error.stack);
res.status(417).json(err).end();
}
});
function writeCustomError(message) {
var errorObject = {};
errorObject.message = message;
errorObject.code = 10001; // as you want
errorObject.status = "failed";
return errorObject;
}
in try block you can also handle logical errors using same function i.e writeCustomError
So if you use this approach you can also implement end-to-end encryption and send only eP('encrypted payload') and eK('encryption Key'),by doing this end users and bad end users even can not evaluate your serve API calls.
If you are thinking how will you route different paths at server then simplest solution is send uri in payload from client to server for e.g
User wants to reset password :-
then
call api like this
https://domain.tld/server/execute and send Json object in payload like this {uri:"reset-password",old:"",new:""}.
at backend
use
app.all("/server/execute", function (req, res) {
try {
// decrypt payload
req.url = payload.uri;
next();
} catch (error) {
// error handling
var err = writeCustomError(error.message || error.errmsg || error.stack);
res.status(417).json(err).end();
}
});
app.all("/reset-password", function (req, res) {
try {
// reset logic
} catch (error) {
// error handling
var err = writeCustomError(error.message || error.errmsg || error.stack);
res.status(417).json(err).end();
}
});
so in this way only developer know where password reset logic and how it can called and what parameters are required.
I will also suggest you to create different router files for express like QueryRouter,InsertRouter etc.
Also try to implement end-to-end encryption.Any query regarding post,kindly comment it.

How to change multiple properties of an object at once in Javascript?

I'm trying to update a document using mongoose. And I would like to update multiple properties at once. I've tried using the spread operator to clone the document and override the properties that need to be updated with user input, but it returned error document.save is not a function. I kind of understand why it throws the errors. Because it's not the same object, it's just a clone so it doesn't have save method. Please correct me if I'm wrong.
So my question is: is there a way to update the object like with spread operator?
My code:
router.put("/posts/update", (req, res)=>{
const {id, updatedFields} = req.body;
Post.findById(id).exec((err, post)=>{
if(err) throw err;
post = {...post, updatedFields};
post.save();
res.json(post);
})
});
You can use Object.assign
post= Object.assign(post, updatedFields);
The assign function can take as many arguments as you would like. Each argument is applied on the leftmost, from left to right. So if multiple arguments hold the same property, its value would be the one from the right.
Use Object.assing it'll not change object reference.
I would suggest instate of throwing err, send the response with some status code.
router.put("/posts/update", (req, res) => {
const {
id,
updatedFields
} = req.body;
Post.findById(id).exec((err, post) => {
if (err) return res.status(400).json({err, msg: 'Some msg'});
post = Object.assign(post, updatedFields);
post.save();
res.json(post);
})
});

Should I validate get parameter with mongoose

In my routing I have get request:
router.get('/getOne', auth(), mapController.getOne);
I'm passing id parameter in url and doing mongo query with mongoose in mapController like this:
exports.getOne = async(req, res, next) => {
try {
const mapData = await Map.findById(req.query.id);
res.json(mapData);
} catch (e) {
return next(e);
}
};
previously I was working with PHP where we were escaping parameters to avoid sql injection. Here I'm not doing anything similiar I just pass req.query.id straight to findById method. Is everything okey with above code when it comes to security?
In this case Mongoose would detect that you are passing a string and internally would try to convert it to mongodb ObjectId. If that fails it would not run the query. The error you would get is:
UnhandledPromiseRejectionWarning: CastError: Cast to ObjectId failed
for value "VALUE HERE" at path "_id" for model "Map"
So as you see you either pass an actual mongoDb ObjectId or a valid string which can be casted to one. Anything else would produce a CastError by Mongoose.

How do I specify MongoDB key/value parameters in an $http.get with an AngularJS controller?

I'm using the MEAN stack for a web app. In my controller.js, I have the following:
var refresh3 = function() {
$http.get('/users').success(function(response) {
console.log("I got the data I requested");
$scope.users = response;
$scope.contact3 = "";
});
};
refresh3();
This pulls back every object in the "users" collection from my MongoDB. How could I add parameters to this to bring back, for example, only objects where "name" : "Bob" ? I have tried adding parameters in the '/users' parentheses using:
$http.get('/users', {params:{"name":"Bob"}})
I've also tried variants of that, but no luck. Any help would be appreciated!
If your server is receiving the data
(and it should, as $http.get('/users', {params:{"name":"Bob"}}) is correct)
On server side, make use of the query string:
req.query
like so:
app.get('/users', function(req,res){
if(req.query.name){
db.users.find({"name":req.query.name},function (err, docs) { console.log(docs); res.json(docs); });
}
else{
db.users.find(function (err, docs) { console.log(docs); res.json(docs); });
}
});
WHAT WAS THE ISSUE?
You hinted in your comments that your server was set to respond to the app.get('/users') GET request like so:
db.users.find(function (err, docs) {
// docs is an array of all the documents in users collection
console.log(docs); res.json(docs); }); });
So I believe that your angularjs $http get is correct, and your server is receiving the parameters {"name":"Bob"} as it should;
it just doesn't know what to do with them:
all it is set to do is to return the whole collection in the particular case of a app.get('/users') GET request.
HOW TO CONFIGURE YOUR SERVER FOR REST
You do not have to re-invent the wheel on the server.
Rather, you could consider using a middleware to automate the task (in the present case, the task is to issue a proper MongoDb request when you receive a get query with parameters from the client)
e.g. express-restify-mongoose middleware

Mongoose Error: `{"message":"No matching document found.","name":"VersionError"}`

I've been trying to figure out why my HTTP Put has not been working after I use it once. When I click a button, I push the current user's id into an array like so:
$scope.currentUser = {
'eventsAttending' = [];
}
$scope.attending = function(event, id){
if($cookieStore.get('token')){
$scope.currentUser = Auth.getCurrentUser();
}
$scope.currentUser.eventsAttending.push(event._id);
$http.put('/api/users/' + $scope.currentUser._id, $scope.currentUser)
.success(function(data){
console.log("Success. User " + $scope.currentUser.name);
});
}
And my HTTP Put function is like so:
var express = require('express');
var controller = require('./user.controller');
var config = require('../../config/environment');
var auth = require('../../auth/auth.service');
router.get('/:id', controller.getEvents);
var router = express.Router();
exports.update = function(req, res) {
if(req.body._id) { delete req.body._id; }
User.findById(req.params.id, function (err, user) {
if (err) { return res.send(500, err); }
if(!user) { return res.send(404); }
var updated = _.merge(user, req.body);
updated.markModified('eventsAttending');
updated.save(function (err) {
if (err) { return res.send(500, err); }
return res.json(200, user);
});
});
};
In my HTML page I have multiple events I can attend, and each event has the button called Attend where I call $scope.attending and the function is called and the HTTP Put occurs. All of this works for the first event I choose to attend. However, when I click the Attend button for another event, I get an error that says:
{"message":"No matching document found.","name":"VersionError"}
And I have no idea why. The error occurs when I try to do updated.save() in the mongoose call and I get res.send(500, err)
I tried to look at http://aaronheckmann.blogspot.com/2012/06/mongoose-v3-part-1-versioning.html to solve the issue as I did some googling but I keep getting an error that says:
Undefined type at `versionKey`
Did you try nesting Schemas? You can only nest using refs or arrays.
Upon adding into my schema:
var UserSchema = new Schema({
versionKey: 'myVersionKey',
...
I also tried to change the .save() function into .update() as someone suggested it online but that seemed to give me even more errors. Any ideas how to fix this? That would be much appreciated!
I think the issue you may be experiencing (as was the case when I was getting this error from a similar action) is that after the first update, the '__v' VersionKey property on the newly updated document has changed, but you might not be updating that property on the object you have in the browser. So when you go to update it again, you're sending the old '__v' VersionKey, (even though you updated the 'eventsAttending' property) and that document conflicts the newer VersionKey. This would assume that the Auth.getCurrentUser(); function returns the whole document object from mongo.
What I did to fix this was simply add delete entity.__v; to delete the old VersionKey from the document before sending it with the request. Better yet, I'd recommend updating the properties your API sends back when returning documents so this issue doesn't happen in the first place.

Categories

Resources