Mongoose Populate with express res.json() breaks - json

So I'm selecting Activities from the mongodb and populating User for each.
var query = Activity.find(query).populate("user");
return query.sort({created:"desc"}).exec(function(err, activities) {
debugger;
if (!err) {
return res.json(activities);
} else {
res.status(400).json(err);
}
});
As you can see I have a debugger; breakpoint is there, When I'm pring activities it prints an array of activities with the user object populated.
Also when I'm calling something like activities[0].toJSON() I get everything good!
But the response comes back with the user property empty !
I looked into the source of express.response.json(OBJ) and saw this line:
var body = JSON.stringify(val, replacer, spaces);
val is my activities
When calling JSON.stringify(activities) it will create a json with an empty user field.. any suggestions ?

Try the lean option. That gives back plain JS objects with no mongoose weirdness. Also, your error handling seems a little awkward, can be simplified.
var query = Activity.find(query).populate("user");
query.sort({created:"desc"}).lean().exec(function(err, activities) {
if (err) return res.status(400).json(err);
res.json(activities);
});
I would go even further, not hard-coding error sending in routes but simply passing along via if (err) return next(err) to error-handling middleware defined elsewhere in your app. You can still set the status, then use detection in your middleware, something like this:
app.use(function(err, req, res, next){
err.status = err.status || 500;
res.status(err.status).json(err);
});

Related

Proper way of handling errors and sending JSON back for API

I am new to js/node/express, and I have been working on this application where I have the following code to handle user registration:
const Account = require('../models/Account.js')
module.exports = {
// TODO: Check why Postman hangs on POST request for this
async register (req, res, next) {
if (req.body.email && req.body.password) {
var newAccount = new Account()
newAccount.email = req.body.email
newAccount.password = newAccount.generateHash(req.body.password)
const account = Account.create({email: newAccount.email, password: newAccount.password}, function (err, res) {
if (err) {
console.log('could not insert. Check error.')
// CANT CALL res.status(400).send({ error: 'email already exists'})
res.status(500)
return next(err)
}
res.status(400).send({
error: 'exists'
})
})
console.log(`inserted account ${newAccount.email}`)
res.send(account.toJSON())
}
}
}
I read this post about how to properly send JSON data back in order to build a proper REST API but ran into some issues.
When I do the call to res.status(400) I get an error that res.status is not a function. Is that because res is not available in that if statement? If it isn't how then, do I properly send a 400 (or any error status) in a case like this?
I want to be able to send an error message if the saving into my mongo db fails, or send back the created user if the insertion was successful.
If there is anything out there that I can read Id love to read some of that as well.
When I do the call to res.status(400) I get an error that res.status is not a function.
That's because you are defining res as an argument to the callback in this line:
const account = Account.create({email: newAccount.email, password: newAccount.password},
function (err, res) {
And that res hides the higher scoped res. The solution is to not have a name conflict. Change the name of this res to be accountRes or something like that. You have to be aware of name conflicts in declared argument names when nesting inline functions.
It also looks like:
res.send(account.toJSON())
is in the wrong place. You will send that BEFORE Account.create() finishes its asynchronous work. That probably needs to be inside the callback.
Speaking of proper error handling, if this if (req.body.email && req.body.password) test fails, then you don't send any response at all. You need to always send some sort of response to an http request. I'd suggest adding an else to that if and send an appropriate response.

synchronous mysql queries in nodejs

I'm pretty new to nodejs and I'm having some difficulties to understand how to use the mysql connection object.
My problem is not in the code but in the design pattern.
lets say I have a user module
module.exports = function(){
return{
id: "",
load: function(id){
var sql = 'SELECT * from users where id = '+ DB.escape(id);
console.log(1);
DB.query(sql, function (err, rows) {
this.id = rows[0].id; // not working
console.log(rows[0].id); // prints the id 4
console.log(2);
});
console.log(3);
}
}
}
from outside the module i run the next code
var user = require('../modules/user');
var selected_user = user();
console.log("entering users me route");
selected_user.load(4);
console.log("user id is " + selected_user.id); //This does not print the id 4
when I run the code, the console logs 1, then 3, and then 2.
This is due to the asynchronous flow of node js.
But if I'm building a website, and I need the query to end in order to populate my user object before I send the HTML to the browser???
What's the right way to do it ?
Also when I try to populate the id property of user in the id i receive from the DB it does not work.
Any ideas?
Thanks
There are several ways to do this. I would go with Promises.
Suppose you have an asynchronous function "getUsers".
It looks like this:
function getUsers() {
longQuery(function(err, result){
// What to do with result?
});
You need to rewrite it to be able to use the result.
Let's try:
function getUsers() {
return new Promise(function(resolve, reject) {
longQuery(function(err, result){
if(err) reject(err)
else resolve(result)
});
});
Now this function returns a promise. What do we do with that promise?
function handleRequest(req, res) {
getUsers().then(function(result) {
// Do stuff with result
res.send(myProcessedData);
}).catch(function(err) {console.log(err)};
}
This could also have been done with callbacks, passing the response object as a parameter to the query function, and many other ways, but I think promises are a very elegant way for handling this.
this.id = rows[0].id; // not working
The above line is not working because you are setting it to this.id from inside a callback function. When you are inside a callback function this does not mean the this in the main object.
For more discussion about this: see How to access the correct `this` context inside a callback?
To tackle the asynchronous nature of javascript you can either use promise like the answer from matanso or you can pass a callback function to your load method. So your load: function(id) method will be load: function(id, callbackFunction) and call the callback function when you get all the data that you need.

JSON Vulnerability Protection using express JS

I would like to know how can I use JSON Vulnerability Protection with express.js.
http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerability.aspx
The problem is I used to write res.send(jsonObj) from controllers, which will sent data directly to the client.
But I want to intercept the response and modify it with something and send to the client. The client can then undo the modification and retrieve the original data.
I saw the res.format function, but it is not working as for my need.
I was using res.json instead of res.send to send JSON, so I modified the code from your answer thus:
app.use(function (req, res, next) {
res.json = function (data) {
var strData = typeof data == 'object' ? JSON.stringify(data) : data;
strData = expressOptions.jsonPrefix + strData;
res.set('Content-Type', 'text/json');
res.send.call(res, strData);
};
next();
});
Although I've implemented this "just to be sure", I don't think this is a serious vulnerability. If you read this, which is linked from this (which is where I think you got your inspiration to write your Express middleware), it seems that the JSON Vulnerability doesn't exist in "modern" browsers, as in, as far back as IE 6 and FireFox 3.
So I'm not sure why AngularJS is telling people to implement this protection these days. Would appreciate if someone enlightened me in the comments! :)
Finally I ended up doing this:
app.use(function (req, res, next){
var actualSend = res.send;
res.send = function (data) {
if (typeof data == "object") {
var strData = expressOptions.jsonPrefix + JSON.stringify(data);
res.set('Content-Type', 'text/json');
actualSend.call (res, strData);
} else {
actualSend.call (res, data);
}
};
next();
});
Where expressOptions.jsonPrefix is the prefix I wanted.
I added it before my route configurations.

Node Exports Function Returning Undefined

I have a an exports function I'm calling that should return a json array of draft results. In the route below in app.js, when I console.log draft_results, I get undefined
app.get('/draft-results', function(req, res) {
var draft_results = fantasy.getDraftResults(req, res);
console.log(util.inspect(draft_results, false, null));
//looks in views folder by default
res.render('draft-results', {
draft_results: draft_results
});
});
In my other file, this is the function that should be returning the json array. If i console.log draft, the data is there.
exports.getDraftResults = function(req, res, cb) {
oauth.get(
"http://fantasysports.yahooapis.com/fantasy/v2/league/" + conf.LEAGUE_ID + "/draftresults?format=json",
req.user.accessToken,
req.user.tokenSecret,
function(e, data, resp) {
if (e) console.error(e);
data = JSON.parse(data);
var draft = data.fantasy_content.league[1].draft_results;
res.json(draft);
}
);
};
I feel like I am returning the data incorrectly, and I can't seem to find any other good examples out there. Could someone please assist?
getDraftResults() is asynchronous. That means the results it generates occur sometime later. Thus, it cannot return its results directly from the function like you are trying to use.
It is unclear what you want to be doing here. Inside of getDraftResults() you are creating a JSON response back to the web request that started all this. That, in itself would be fine and will work as you have it (except the error handling is missing).
But, in your app.get() handler, you have completely different code that seems to thing that getDraftResults() is going to return a value (it has no return value at all) and then you will later use that return value.
So, if you just want getDraftResults to make a JSON response to the original web request, it's already doing that and you can remove the rest of what you have in the app.get() handler. If that's not really what you want to do and you want to use the response from getDraftResults() inside of the app.get() handler, then you will have to change the design of both functions and likely pass a callback to getDraftResults() so the callback can supply the asynchronous response and you can then continue the rest of the app.get() functionality in that callback.
If you're trying to do the latter, then here's a scaffolding (I don't know exactly what you're trying to accomplish so I can't be too detailed here):
app.get('/draft-results', function(req, res) {
fantasy.getDraftResults(req, function(err, draft_results) {
if (err) {
// send some sort of error response here
console.error(err);
return;
}
console.log(util.inspect(draft_results, false, null));
//looks in views folder by default
res.render('draft-results', {
draft_results: draft_results
});
});
});
exports.getDraftResults = function(req, cb) {
oauth.get(
"http://fantasysports.yahooapis.com/fantasy/v2/league/" + conf.LEAGUE_ID + "/draftresults?format=json",
req.user.accessToken,
req.user.tokenSecret,
function(e, data, resp) {
if (e) {
console.error(e);
cb(e);
return;
}
data = JSON.parse(data);
var draft = data.fantasy_content.league[1].draft_results;
// send results back to caller
cb(null, draft);
}
);
};

Convert Mongoose docs to json

I returned mongoose docs as json in this way:
UserModel.find({}, function (err, users) {
return res.end(JSON.stringify(users));
}
However, user.__proto__ was also returned. How can I return without it? I tried this but not worked:
UserModel.find({}, function (err, users) {
return res.end(users.toJSON()); // has no method 'toJSON'
}
You may also try mongoosejs's lean() :
UserModel.find().lean().exec(function (err, users) {
return res.end(JSON.stringify(users));
});
Late answer but you can also try this when defining your schema.
/**
* toJSON implementation
*/
schema.options.toJSON = {
transform: function(doc, ret, options) {
ret.id = ret._id;
delete ret._id;
delete ret.__v;
return ret;
}
};
Note that ret is the JSON'ed object, and it's not an instance of the mongoose model. You'll operate on it right on object hashes, without getters/setters.
And then:
Model
.findById(modelId)
.exec(function (dbErr, modelDoc){
if(dbErr) return handleErr(dbErr);
return res.send(modelDoc.toJSON(), 200);
});
Edit: Feb 2015
Because I didn't provide a solution to the missing toJSON (or toObject) method(s) I will explain the difference between my usage example and OP's usage example.
OP:
UserModel
.find({}) // will get all users
.exec(function(err, users) {
// supposing that we don't have an error
// and we had users in our collection,
// the users variable here is an array
// of mongoose instances;
// wrong usage (from OP's example)
// return res.end(users.toJSON()); // has no method toJSON
// correct usage
// to apply the toJSON transformation on instances, you have to
// iterate through the users array
var transformedUsers = users.map(function(user) {
return user.toJSON();
});
// finish the request
res.end(transformedUsers);
});
My Example:
UserModel
.findById(someId) // will get a single user
.exec(function(err, user) {
// handle the error, if any
if(err) return handleError(err);
if(null !== user) {
// user might be null if no user matched
// the given id (someId)
// the toJSON method is available here,
// since the user variable here is a
// mongoose model instance
return res.end(user.toJSON());
}
});
First of all, try toObject() instead of toJSON() maybe?
Secondly, you'll need to call it on the actual documents and not the array, so maybe try something more annoying like this:
var flatUsers = users.map(function() {
return user.toObject();
})
return res.end(JSON.stringify(flatUsers));
It's a guess, but I hope it helps
model.find({Branch:branch},function (err, docs){
if (err) res.send(err)
res.send(JSON.parse(JSON.stringify(docs)))
});
I found out I made a mistake. There's no need to call toObject() or toJSON() at all. The __proto__ in the question came from jquery, not mongoose. Here's my test:
UserModel.find({}, function (err, users) {
console.log(users.save); // { [Function] numAsyncPres: 0 }
var json = JSON.stringify(users);
users = users.map(function (user) {
return user.toObject();
}
console.log(user.save); // undefined
console.log(json == JSON.stringify(users)); // true
}
doc.toObject() removes doc.prototype from a doc. But it makes no difference in JSON.stringify(doc). And it's not needed in this case.
Maybe a bit astray to the answer, but if anyone who is looking to do the other way around, you can use Model.hydrate() (since mongoose v4) to convert a javascript object (JSON) to a mongoose document.
An useful case would be when you using Model.aggregate(...). Because it is actually returning plain JS object, so you may want to convert it into a mongoose document in order to get access to Model.method (e.g. your virtual property defined in the schema).
PS. I thought it should have a thread running like "Convert json to Mongoose docs", but actually not, and since I've found out the answer, so I think it is not good to do self-post-and-self-answer.
You can use res.json() to jsonify any object.
lean() will remove all the empty fields in the mongoose query.
UserModel.find().lean().exec(function (err, users) {
return res.json(users);
}
It worked for me:
Products.find({}).then(a => console.log(a.map(p => p.toJSON())))
also if you want use getters, you should add its option also (on defining schema):
new mongoose.Schema({...}, {toJSON: {getters: true}})
Try this options:
UserModel.find({}, function (err, users) {
//i got into errors using so i changed to res.send()
return res.send( JSON.parse(JSON.stringify(users)) );
//Or
//return JSON.parse(JSON.stringify(users));
}
Was kinda laughing at how cumbersome this was for a second, given that this must be extremely common.
Did not bother digging in the docs and hacked this together instead.
const data = await this.model.logs.find({ "case_id": { $regex: /./, $options: 'i' }})
let res = data.map(e=>e._doc)
res.forEach(element => {
//del unwanted data
delete element._id
delete element.__v
});
return res
First i get all docs which have any value at all for the case_id field(just get all docs in collection)
Then get the actual data from the mongoose document via array.map
Remove unwanted props on object by mutating i directly