Mongoose: Sort alphabetically - node.js

I have a User model
var User = mongoose.model('Users',
mongoose.Schema({
username: 'string',
password: 'string',
rights: 'string'
})
);
I want to get all the users, sorted alphabetically by username. This is what I have tried
User.find({}, null, {sort: {username: 1}}, function (err, users) {
res.send(users);
});
However, this does not sort the users alphabetically. How can I sort alphabetically?
EDIT: I got confused because I was expecting a "purely alphabetically" sort from Mongoose, not one where Z > a. Basically, I wanted a sort based on username.toLowerCase().

This question and answer are a few years old, and from what I can tell there is now a correct way to do this. Providing this for future searchers:
User.find().collation({locale:'en',strength: 2}).sort({username:1})
.then( (users) =>{
//do your stuff
});
You could also index on username without case sensitivity:
UserSchema.index({username:1}, {collation: { locale: 'en', strength: 2}});
strength:1 is another option - best to refer to the documentation to decide which works best for you.
For the details of all this, look here.

EDIT: Per the comment the issue turns out to be sorting on toLowerCase(username). MongoDB doesn't have a built in method for complex sorting. So there are essentially two ways to go:
Add a usernameLowerCase field to the Schema. This is the better option if you need to do this a lot.
Perform an aggregation with a projection using the $toLower operator to dynamically generate a usernameLowerCase field. This comes with performance and memory caveats, but it may be the more convenient choice.
Original Answer: Here's a complete example that sorts correctly using the specific code from the question. So there must be something else going on:
#! /usr/bin/node
var mongoose = require('mongoose');
mongoose.connect('localhost', 'test');
var async = require('async');
var User = mongoose.model('Users',
mongoose.Schema({
username: 'string',
password: 'string',
rights: 'string'
})
);
var userList = [
new User({username: 'groucho', password: 'havacigar', rights: 'left'}),
new User({username: 'harpo', password: 'beepbeep', rights: 'silent'}),
new User({username: 'chico', password: 'aintnosanityclause', rights: 'all'})
];
async.forEach(userList,
function (user, SaveUserDone) {
user.save(SaveUserDone);
},
function (saveErr) {
if (saveErr) {
console.log(saveErr);
process.exit(1);
}
User.find({}, null, {sort: {username: 1}}, function (err, users) {
if (err) {
console.log(err);
process.exit(1);
}
console.log(users);
process.exit(0);
});
}
);

Related

Update element in Array of Mongoose schema

I am trying to update one element of snippets in my mongoose schema.
My Mongoose schema.
const Schema = new mongoose.Schema({
// ...
createdAt: Date,
snippets: {} // here I push ['string..', ['array of strings..']]
})
Here's a view of snippets in Compass.
Problem with the code below is that it completely erases other elements stored, other than that it works. Unable to specify that I want to update snippets[0], not entire thing..?
User.findOneAndUpdate({ username: req.session.user.username },
{ $set: { snippets: [snippet] } }, callback)
Tried using findOne andsave but it wouldn't update the db.
const snippet = [req.body.code, [req.body.tags]]
User.findOne({ username: req.session.user.username }, function (err, fetchedUser) {
if (err) console.log(err)
fetchedUser.snippets[req.params.id] = snippet // should be set to new snippet?
fetchedUser.save(function (err, updatedUser) {
if (err) console.log(err)
console.log('edited')
// ...
})
})
Any suggestions?
I thought I tried this earlier, but apparantly not.
Using fetchedUser.markModified('snippets') solved my issue with findOne/save not actually saving to DB.

Nested query with mongoose

I have three models: User, Post and Comment
var User = new Schema({
name: String,
email: String,
password: String // obviously encrypted
});
var Post = new Schema({
title: String,
author: { type: Schema.ObjectId, ref: 'User' }
});
var Comment = new Schema({
text: String,
post: { type: Schema.ObjectId, ref: 'Post' },
author: { type: Schema.ObjectId, ref: 'User' }
});
I need to get all posts in which the user has commented.
I know it should be a very simple and common use case, but right now I can't figure a way to make the query without multiple calls and manually iterating the results.
I've been thinking of adding a comments field to the Post schema (which I'd prefer to avoid) and make something like:
Post.find()
.populate({ path: 'comments', match: { author: user } })
.exec(function (err, posts) {
console.log(posts);
});
Any clues without modifying my original schemas?
Thanks
You have basically a couple of approaches to solving this.
1) Without populating. This uses promises with multiple calls. First query the Comment model for the particular user, then in the callback returned use the post ids in the comments to get the posts. You can use the promises like this:
var promise = Comment.find({ "author": userId }).select("post").exec();
promise.then(function (comments) {
var postIds = comments.map(function (c) {
return c.post;
});
return Post.find({ "_id": { "$in": postIds }).exec();
}).then(function (posts) {
// do something with the posts here
console.log(posts);
}).then(null, function (err) {
// handle error here
});
2) Using populate. Query the Comment model for a particular user using the given userId, select just the post field you want and populate it:
var query = Comment.find({ "author": userId });
query.select("post").populate("post");
query.exec(function(err, results){
console.log(results);
var posts = results.map(function (r) { return r.post; });
console.log(posts);
});

Expressjs rest api how to deal with chaining functionality

I am building a restful API using express, mongoose and mongodb. It works all fine but I have a question about how to deal with requests that contain more functionality than just one find, delete or update in the database. My user model looks as follows:
var UserSchema = new Schema({
emailaddress: {type: String, unique: true},
firstname: String,
lastname: String,
password: String,
friends: [{type: Schema.Types.ObjectId, unique: true}]
});
As you can see is the friends array just an array of ObjectIds. These ObjectIds refer to specific users in the database. If I want to retrieve an array of a user's friends I now have to look up the user that makes the request, then find all the users that have the same id as in the friends array.
Now it looks like this:
methods.get_friends = function(req, res) {
//find user.
User.findOne({_id: req.params.id}, function(err, user, next) {
if(err) next(err);
if(user) {
console.log(user);
//find friends
User.find({_id: {$in: user.friends}}, {password: 0}).exec(function (err,
friends, next) {
if(err) next(err);
if(friends) {
res.send(friends);
};
});
}
Would it be possible to seperate the lookup of the user in a certain method and chain the methods? I saw something about middleware chaining i.e. app.get('/friends', getUser, getFriend)but would that mean that I have to alter the req object in my middleware (getUser) method and then pass it on? How would you solve this issue? Would you perhaps change the mongoose model and save all friend data (means that it could become outdated) or would you create a method getUser that returns a promise on which you would collect the friend data?
I will be grateful for all the help I can get!
Thank you in advance.
Mongoose has a feature called population which exists to help in these kinds of situations. Basically, Mongoose will perform the extra query/queries that are required to load the friends documents from the database:
User.findOne({_id: req.params.id})
.populate('friends')
.exec(function(err, user) {
...
});
This will load any related friends into user.friends (as an array).
If you want to add additional constraints (in your example, password : 0), you can do that too:
User.findOne({_id: req.params.id})
.populate({
path : 'friends'
match : { password : 0 },
})
.exec(function(err, user) {
...
});
See also this documentation.

Check if ID exists in a collection with mongoose

For instance, I have a collection User:
var mongoose = require('mongoose');
var UserSchema = new mongoose.Schema({
email: String,
googleId: String,
facebookId: String,
displayName: String,
active: Boolean
});
module.exports = mongoose.model('User', UserSchema);
And then I have an ID:
var userID = "some-user-id"
What is the right way to just check if this id exists in the User collection. I don't need it to read the file or return it, I just need the true or false value.
Here is one way to achieve it:
User.findOne({
_id: userID
}, function (err, existingUser) {
But is there faster and more efficient way?
Use count rather than findOne.
This will (under the hood) cause mongoose to use find : http://docs.mongodb.org/manual/reference/method/db.collection.count
findOne() will read + return the document if it exists
On the other hand, find() just returns a cursor (or not) and only reads the data if you iterate over the cursor.
So in our case, we're not iterating over the cursor, merely counting the results returned.
User.countDocuments({_id: userID}, function (err, count){
if(count>0){
//document exists });
}
});
You can now use User.exists() as of September 2019 like so:
const doesUserExit = await User.exists({ _id: userID });
From the docs:
Under the hood, MyModel.exists({ answer: 42 }) is equivalent to
MyModel.findOne({ answer: 42 }).select({ _id: 1 }).lean().then(doc =>
!!doc)
The accepted answer is fine for small collections.
A faster way on larger collections is to simply use this:
const result = await User.findOne({ _id: userID }).select("_id").lean();
if (result) {
// user exists...
}
// or without "async/await":
User.findOne({ _id: userID }).select("_id").lean().then(result => {
if (result) {
// user exists...
}
});
It won't return all fields. I believe they are currently working on a new feature to support what you (and I) want.
In the meantime you could create a plugin, very simple and reusable.
Create an any.js file with this code:
module.exports = function any(schema, options) {
schema.statics.any = async function (query) {
const result = await this.findOne(query).select("_id").lean();
return result ? true : false;
};
}
Then in your model you do this:
var mongoose = require('mongoose');
const any = require('./plugins/any'); // I'm assuming you created a "plugins" folder for it
var UserSchema = new mongoose.Schema({
email: String,
googleId: String,
facebookId: String,
displayName: String,
active: Boolean
});
UserSchema.plugin(any);
module.exports = mongoose.model('User', UserSchema);
...and use it like this:
const result = await User.any({ _id: userID });
if (result) {
// user exists...
}
// or without using "async/await":
User.any({ _id: userID }).then(result => {
if (result) {
// user exists...
}
});
OR you can simply use exists function, without making any async/await:
myData = {_id: userID};
User.exists(myData,(error, result)=>{
if (error){
console.log(error)
} else {
console.log("result:", result) //result is true if myData already exists
}
});
You can play with the result now!
User.exists({ _id: userID }).then(exists => {
if (exists) {
res.redirect('/dashboard')
} else {
res.redirect('/login')
}
})
More info can be found at Mongoose docs.
The accepted answer is excellent, but I would really recommend using estimatedDocumentCount() if you are searching existing document by an indexed property (like _id of X).
On the other hand, this should actually work better and is cleaner.

Is it make sense to wrap mongoose model to own API?

I'm trying to understand is it make sense to make own API for working with Mongoose models?
Let's say we have the simple Mongoose user model:
var UserSchema = new mongoose.Schema({
email: { type: 'string', required: true, unique: true, lowercase: true },
password: { type: 'string', required: true },
name: {type: 'string'}
});
var UserModel = mongoose.model('User', UserSchema);
For a abstract application, User model should have methods like 'create', 'delete', 'update', 'find', 'authenticate' and so on. So I have two approach to achieve this purpose:
Include these methods into a Mongoose Model like the following:
UserSchema.static('create', function (data, callback) {
var user = new User(data);
user.save(function (err) {
if (err) return callback(err);
return callback(null, user);
});
});
Wrap a method in a custom User class like this:
UserProvider = function(){};
UserProvider.prototype.create = function(data, callback) {
var user = new User(data);
user.save(function (err) {
if (err) return callback(err);
return callback(null, user);
});
};
In the first one I can to create a new user like this:
UserModel.create({name: 'test'}, function (err, user) {
if (err) {// do something}
});
And in the second one I can create a new similar:
var userProvider= new UserProvider();
userProvider.create({name: 'test'}, function (err, user) {
if (err) {// do something}
});
Although these approaches look similar I feel I need choose that don't break Mongoose API in a future.
Please tell me which is approach looks better for mongoose models?
Mongoose models have native create, update, and find methods, so method one would already break Mongoose if you tried to add those.
You could use method two (which feels messy to me), but it's probably cleaner to use the existing Mongoose methods as your provider API pattern and just add the ones you need that are not provided natively via static functions. Either that or add your own full set of methods that have names that use a unique prefix (e.g. my_create, my_update, etc.).
But make sure it's clear to you why you're adding an abstraction layer, as the added indirection and complexity doesn't come free.

Resources