Skip to content

Instantly share code, notes, and snippets.

@jdnichollsc
Last active August 13, 2025 16:23
Show Gist options
  • Save jdnichollsc/c4edec104eec3769954b to your computer and use it in GitHub Desktop.
Save jdnichollsc/c4edec104eec3769954b to your computer and use it in GitHub Desktop.
Search users with fullname in MongoDB using Mongoosejs and Aggregation Framework in Node.js
var mongoose = require('mongoose');
var validate = require('mongoose-validator');
var Schema = mongoose.Schema;
var crypto = require('crypto');
var utilities = require('../services/utilities');
var userSchema = new Schema({
firstname: { type : String, trim : true },
lastname: { type : String, trim : true },
username: { type: String, required: true, unique: true, lowercase: true, trim : true, index : true },
password: { type: String, validate: passwordValidator },
email: { type : String, trim : true, validate: emailValidator },
phone: { type : String, trim : true },
address: { type : String, trim : true },
groups: [{ type : Schema.Types.ObjectId, ref : 'Group' }],
created_at: Date,
updated_at: Date
},
{
toObject: { virtuals: true },
toJSON: { virtuals: true }
});
userSchema.virtual('fullname').get(function () {
return [this.firstname, this.lastname].filter(Boolean).join(' ');
});
userSchema.pre('save', function (next) {
if (this.password) {
this.password = encrypt(this.password);
}
utilities.reviewDate(this);
next();
});
function encrypt(text) {
return crypto.createHash("SHA512").update(text).digest("base64");
}
var emailValidator = [
validate({
validator: 'isEmail',
message: 'Please fill a valid email address'
})
];
var passwordValidator = [
validate({
validator: 'isLength',
arguments: [6, 50],
message: 'Password should be between {ARGS[0]} and {ARGS[1]} characters'
}),
validate({
validator: 'matches',
arguments: /\d/,
message: 'Password should contain numbers'
}),
validate({
validator: 'matches',
arguments: /[a-zA-Z]/,
message: 'Password should contain letters'
}),
validate({
validator: 'matches',
arguments: /[A-Z]/,
message: 'Password must contain one uppercase letter'
}),
validate({
validator: 'matches',
arguments: /[\!\@\#\$\%\^\&\*\(\)\_\+\.\,\;\:]/,
message: 'Password should contain a special characters like !@#$%^&*()_+'
})
];
module.exports = mongoose.model('User', userSchema);

MongoDB’s Aggregation Framework

It processes documents through a pipeline of stages, where each stage transforms the documents before passing them to the next

The aggregation framework is MongoDB’s built-in way to do multi-document analysis like this — counting, grouping, transforming and filtering in one pipeline.

Aggregation Framework in MongoDB works very much like an RxJS pipeline, but for database documents. Pipeline stages are like RxJS operators, Documents flow through the pipeline like events through a stream and Each stage outputs a new transformed stream of documents to the next stage. In MongoDB’s Aggregation Framework, the stream is inside the database engine, so it’s usually much faster because it avoids network round-trips and leverages DB indexes.

Summary

  • Purpose → Transform, filter, join, and analyze data inside MongoDB before it leaves the server.
  • Scope → Operates on sets of documents, passing them through multiple stages in a pipeline.
  • Capabilities:
    • Grouping ($group) like SQL’s GROUP BY.
    • Filtering on group-level conditions ($match after $group) like SQL’s HAVING.
    • Sorting, reshaping, computing new fields ($project), joining collections ($lookup), array manipulation, text search scoring, geospatial ops, etc.
    • Avoids pulling lots of raw data into your app just to process it there.
db.employees.aggregate([
  { $group: { _id: "$EmployeeId", count: { $sum: 1 } } },
  { $match: { count: { $gt: 1 } } }
])
  • ✅ Complex analysis in one trip to the DB.
  • ✅ More efficient — processing happens server-side.
  • ✅ Cleaner code — no need for multiple queries + app-side loops.

In short:

MongoDB Normal query = “Give me these documents as-is.”

Aggregation = “Take all the documents, reshape them, combine them, compute new info and give me only the final processed result.”

var router = express.Router();
var User = require('../models/user');
var authorization = require('../services/authorization'); //Middleware to validate Auth Tokens :)
router.post('/find', authorization.ensureAuthenticated, function (req, res) {
var userName = req.body.search; //userName = 'Juan David Nicholls';
var searchString = new RegExp(userName, 'ig');
User.aggregate()
.project({
fullname: { $concat: ['$firstname', ' ', '$lastname'] },
firstname: 1,
lastname: 1,
username: 1,
email: 1,
phone: 1,
address: 1
})
.match({ fullname: searchString })
.exec(function (err, users) {
if (err) throw err;
res.json({
users: users
});
});
});
var reviewDate = function (instance) {
// get the current date
var currentDate = new Date();
// change the updated_at field to current date
instance.updated_at = currentDate;
// if created_at doesn't exist, add to that field
if (!instance.created_at) {
instance.created_at = currentDate;
}
};
module.exports = {
reviewDate: reviewDate
};
@realsama
Copy link

Can this catch the flip case where the name is passed as lastName + firstName.

@jdnichollsc
Copy link
Author

@shepherd1530 similar to "fullname" you can create another one like this:

reversedname: { $concat: ['$lastname', ' ', '$firstname'] },

and then use an OR conditional from your match method to use both, cheers!

@realsama
Copy link

@shepherd1530 similar to "fullname" you can create another one like this:

reversedname: { $concat: ['$lastname', ' ', '$firstname'] },

and then use an OR conditional from your match method to use both, cheers!

Fix that, thanks.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment