Find a specific object in array with Mongoose and Node.js - node.js

I am trying to return a specific object in an array with mongoose. My document is as follows:
{
"_id": {
"$oid": "577a9345ba1e2a1100624be7"
},
"name": "John Doe",
"password": "$2a$10$NzqAqxTRy8XLCHG8h3Q7IOLBSFCfBJ7R5JqHy1XHHYN.1h074bWJK",
"__v": 0,
"birthDate": "14.07.2016",
"academic": [
{
"about": "asdfasdf",
"to": "asdf",
"from": "asfdasdf",
"institute": "asdfasdf",
"qualification": "asdfasdf",
"_id": {
"$oid": "579111b3e68d489f1ff8b6dc"
}
}
]
}
I want to return that academic object in the list. I am passing in the institute name into the route my code is as follows:
getAcademicInstituteByName: function(req, name, cb){
User.findById(req.user.id, function (err, user) {
if(err) throw err;
if(user){
academic = user.academic.institute(name);
return cb(null, academic);
}
});
But this is not working since I am getting an error saying user.academic.institute is not a function. Any help would be greatly appreciated

user.academic.institute is an array, so you can use regular array operations to find the entry you're interested in:
var academic = user.academic.institute.filter(i => i.institute === name)
.pop();
return cb(null, academic);

academic = user.academic.institute;
this should work, though I haven't tested it.

Related

How to build a search endpoint in a API to find and filter results from a database

In my Node API and MongoDB, I'm trying to build an endpoint to search for data in the DB and get back the results to the client. My search goal is to show results from the Profile collection and in that way, I can build my queries to search by first name, surname, company and the combination of it as an example:
GET search?fn=joe or ?ln=doe or ?cp=Company or ?fn=...&ln=...&cp=...
Practically I can search in different ways and I can get for example all the people working for a company as a result of a search.
I would like to understand how can I achieve that with Mongoose/MongoDB and add also to the query optional a limit/pagination for the coming results.
I tried to make some simple trials but I got stuck as I do not really get it how to proceed next.
const SearchController = {
async getQuery(req, res) {
try {
const { fn, ln, cp } = req.query;
const searchResult = await Profile.find({
$or: [
{ firstname: fn },
{ surname: ln },
{
experience: {
company: cp
}
}
]
});
res.status(200).json(searchResult);
} catch (err) {
res.status(500).json({ message: err.message });
}
}
};
The JSON of a profile:
{
"imageUrl": "https://i.pravatar.cc/300",
"posts": [
"5e3cacb751f4675e099cd043",
"5e3cacbf51f4675e099cd045",
"5e3cacc551f4675e099cd046"
],
"_id": "5e2c98fc3d785252ce5b5693",
"firstname": "Jakos",
"surname": "Lemi",
"email": "lemi#email.com",
"bio": "My bio bio",
"title": "Senior IT developer",
"area": "Copenhagen",
"username": "Jakos",
"experience": [
{
"image": "https://via.placeholder.com/150",
"createdAt": "2020-02-04T13:47:37.167Z",
"updatedAt": "2020-02-04T13:47:37.167Z",
"_id": "5e3975f95fbeec9095ff3d2f",
"role": "Developer",
"company": "Google",
"startDate": "2018-11-09T23:00:00.000Z",
"endDate": "2019-01-05T23:00:00.000Z",
"area": "Copenhagen"
},
{
"image": "https://via.placeholder.com/150",
"createdAt": "2020-02-04T13:59:27.412Z",
"updatedAt": "2020-02-04T13:59:27.412Z",
"_id": "5e3978bf5e399698e20c56d4",
"role": "Developer",
"company": "IBM",
"startDate": "2018-11-09T23:00:00.000Z",
"endDate": "2019-01-05T23:00:00.000Z",
"area": "Copenhagen"
},
{
"image": "https://via.placeholder.com/150",
"createdAt": "2020-02-07T16:35:43.754Z",
"updatedAt": "2020-02-07T16:35:43.754Z",
"_id": "5e3d91dfb3a7610ec6ad8ee3",
"role": "Developer",
"company": "IBM",
"startDate": "2018-11-10T00:00:00.000Z",
"endDate": "2019-01-06T00:00:00.000Z",
"area": "Copenhagen"
}
],
"createdAt": "2020-01-25T19:37:32.727Z",
"updatedAt": "2020-02-04T23:14:37.122Z",
"__v": 0
}
The expected results are for example if I search the first name Joe I should get back all the profiles having as first name Joe. Similar for surname and company.
Please provide comments to allow me to understand if you need more scripts from the original code to see.
EDITED added the code modified of the search
// Models
const { Profile } = require("../models");
// Error handling
const { ErrorHandlers } = require("../utilities");
const SearchController = {
async getQuery(req, res) {
try {
const { fn, ln, cp } = req.query;
const query = {
$or: []
};
if (fn) query.$or.push({ firstname: fn });
if (ln) query.$or.push({ surname: ln });
if (cp) query.$or.push({ "experience.company": cp });
const searchResult = Profile.find(query, docs => {
return docs
});
if ((await searchResult).length === 0)
throw new ErrorHandlers.ErrorHandler(
404,
"Query do not provided any result"
);
res.status(200).json(searchResult);
} catch (err) {
res.status(500).json({ message: err.message });
}
}
};
module.exports = SearchController;
Have tried conditional query and modified your array search query for finding the company,
function findUser(fn, ln, cp) {
const query = {
$or: []
}
if (fn) query.$or.push({ firstname: fn })
if (ln) query.$or.push({ surname: ln })
if (cp) query.$or.push({ "experience.company": cp })
Profile.find(query, function (err, docs) {
if (err) {
console.error(err);
} else {
console.log(docs);
}
});
}
findUser("","","IBM")

How to add an other property to a mongoDB found documents using mongoose?

I want to add an extra property state: 'found' to each found document in mongoose. I have the following code:
router.get('/all', function(req, res, next) {
var allPets = [];
FoundPet.find({}, function(err, pets) {
pets = pets.map((obj) => {
obj.state = 'found';
return obj;
})
res.send(pets)
})
});
I am expecting to have something like this returned:
[
{
"_id": "59c7be569a01ca347006350d",
"finderId": "59c79570c5362d19e4e64a64",
"type": "bird",
"color": "brown",
"__v": 0,
"timestamp": 1506291998948,
"gallery": [],
"state": "found" // the added property
},
{
"_id": "59c7c1b55b25781b1c9b3fae",
"finderId": "59c79a579685a91498bddee5",
"type": "rodent",
"color": "brown",
"__v": 0,
"timestamp": 1506291998951,
"gallery": [],
"state": "found" // the added property
}
]
but I can't get the new property added successfully using the above code, is there any solution for that ?
The reason why it is not working, is because Mongoose by default returns a model for each document returned from the database.
Try the same but using lean(), which returns a plain javascript object instead.
FoundPet
.find({})
.lean()
.exec(function(err, pets) {
pets.forEach((obj) => {
obj.state = 'found';
});
res.send(pets);
});
One approach would be to use the aggregation framework in which you can add the extra field using the $addFields pipeline. This allows you to add new fields to documents and the pipeline outputs documents that contain all existing fields from the input documents and newly added fields.
Hence you can run the aggregate operation as:
router.get('/all', function(req, res, next) {
FoundPet.aggregate([
{
"$addFields": {
"state": { "$literal": "found" }
}
}
]).exec((err, pets) => {
if (err) throw err;
res.send(pets);
});
});

Using two apis one with object id and another with name

I have two api calls to retrieve data from the same collection using different parameters.
The first one is by the ObjectID:
app.get('/api/employees/:id', function(req, res){
Employee.findOne({_id:req.params.id}, function(err, employee){
if(err)
res.send(err);
res.json(employee);
}); });
And the second one retrieves data by the name :
app.get('/api/employees/:name', function(req, res){
Employee.findOne({name:req.params.name}, function(err, employee){
if(err)
res.send(err);
res.json(employee);
});});
I placed both Apis in my code, but only the first one is being called. If I run my code I have something like:
{
"_id": "58b00dd47689fc2f48b9baf4",
"name": "Rohtih",
"dept": "CSE",
"area": "Banglore",
"contact": "9962938489",
"status": "System Engineer",
"salary": "30000",
"__v": 0
},
My mongo collection looks Like this:
{
"_id": "58b00dd47689fc2f48b9baf4",
"name": "Rohtih",
"dept": "CSE",
"area": "Banglore",
"contact": "9962",
"status": "System Engineer",
"salary": "30000",
"__v": 0
},
{
"_id": "58b00df07689fc2f48b9baf5",
"name": "Vaibhav",
"dept": "CSE",
"area": "Banglore",
"contact": "819",
"status": "Manager",
"salary": "300000",
"__v": 0
}
I would like to know how do I use both apis at a time? Is there a mistake in my code, please help me solving this?
As I could understand from your code, you may receive both parameters but you don't know when to use what. What I would separate in two different api calls:
First one is to find by Id:
app.get('/api/employees/findById', function(req, res){
Employee.findOne({_id:req.params.id}, function(err, employee){
if(err)
res.send(err);
res.json(employee);
}); });
Second one is to find by name:
app.get('/api/employees/findByName', function(req, res){
Employee.findOne({name:req.params.name}, function(err, employee){
if(err)
res.send(err);
res.json(employee);
});});
If you don't like any of those approaches, you can leave as it is and verify if the parameters are present, for example:
app.get('/api/employees/find', function(req, res){
let name = req.params.name;
let id = req.params.id;
if(name !== undefined) {
//Find By name here
} else if(id !== undefined) {
//Find by id here
}
}
Hope my answer was helpful.
One approach you can take is determine the value of the req.params.id string if it's a valid ObjectId and then create a query based on the outcome. For example:
app.get('/api/employees/:id', function(req, res){
var query = {};
if (mongoose.Types.ObjectId.isValid(req.params.id)) {
query._id = req.params.id;
} else {
query.name = req.params.id;
}
Employee.findOne(query, function(err, employee){
if(err)
res.send(err);
res.json(employee);
});
});

geoNear not returning any results

I'm trying to use geoNear to return documents in my collection that have latitude and longitude coordinates, but not getting any results even though there are documents in the collection that have latitude and longitude coordinates. I'm using mlab to store my data, for example, here is a document in my collection.
var trucker = db.collection('trucker');
{
"_id": {
"$oid": "581e82d00f192a694bb56679"
},
"latitude": 77.0551642746301,
"longitude": 10.825842664395019,
"loc": [
77.0551642746301,
10.825842664395019
],
}
I have the coordinates in two different ways, because I was trying to see if geoNear would pick up the location, and return the result, but still can't find it. This is how I'm using geoNear currently, to find results
exports.geoNear = function (lat, lon ,res, next)
{
console.log("Inside geoNear");
var job = {};
job.lon = JSON.parse(lon);
job.lat = JSON.parse(lat);
console.log(job.lon);
console.log(job.lat);
trucker.geoNear([job.lon, job.lat], function(err, success){
if(success) {
res(success);
} else {
console.log('Response error' + err);
}
});
};
app.get('/geoFind', function(req, res) {
AM.geoNear('48.99759239999999', '-123.0683089', function(o){
res.send(200,o);
});
});
I am getting this result when visiting localhost:8000/geoFind:
{
"waitedMS": 0,
"results": [],
"stats": {
"nscanned": 0,
"objectsLoaded": 0,
"maxDistance": 0,
"time": 0
},
"ok": 1
}
Now, I have tried many different ways such as the following but get, Response errorMongoError: 'near' field must be point with the following:
trucker.createIndex( { loc : "2dsphere" } )
trucker.geoNear({loc: {type: "Point", coordinates: [job.lon,job.lat]}}, {spherical: true}, function(err, success) {
if(success){
res(success);
} else {
console.log('Response error '+err + job.lon +':' + job.lat);
}
});
Now, I'm wondering how do I use geoNear to return documents with latitude and longitude coordinates? Does latitude and longitude need to be stored in the database a specific way? I tried db.command() as well with geoNear and I always get db.command is not a function, same with trucker.command or trucker.db.db.command or trucker.db.command (anything with command to get data from the database which I think is because I'm using mongoose not mongoclient, because my database it set up with mlab). Nothing I've tried has worked which is why I'm now asking this question as I've tried just about every way imaginable to get results from my database using geoNear.
I got back at it this morning and did some more testing because it didn't make sense to me why I was not getting any results, so I created a brand new collection called location, and inserted 1 document to test and it worked, then 2 documents that look like the following and that worked too:
{
"_id": {
"$oid": "581cc2f430c34502e36eb148"
},
"truckerID": "b233a9eaaedc63730e71a8b542606ee82e0aa5e5",
"name": "Justin",
"email": "Justin#gmail.com",
"company": "Justins Shipping",
"user": "justin1",
"pass": "D6Mvu6rUur758f37eac7010958c14557bb4df9871a",
"phone": "1234567890",
"location": [
-73.9928,
40.7193
]
}
{
"_id": {
"$oid": "581cc2f530c34502e36eb158"
},
"truckerID": "b233a9eaaedc63731e72a8b542606ee82e0aa7a6",
"name": "Alan",
"email": "Alan#gmail.com",
"company": "Alans Shipping",
"user": "alan1",
"pass": "D6Mvu6fUur758f37eac7010958c14557bb4df9872c",
"phone": "1234567890",
"location": [
-122.4194155,
37.7749295
]
}
Apparently, location does have to be in the format,
location: [longitude, latitude]
or geoNear will not be able to find your document.
The really interesting finding to me, was that if you have a collection, with 10 documents, and there are documents in your collection that don't have:
location: [longitude, latitude]
geoNear will not get any results either. If even one document does not have that field, location: [longitude, latitude], geoNear will also not be able to find anything, or return any results after having done some more testing.
The following worked for me after creating those 2 new documents in a new collection for testing purposes using mongoose and mlab.
exports.geoNear = function (lon, lat ,res, next)
{
console.log("Inside geoNear");
var job = {};
job.lon = JSON.parse(lon);
job.lat = JSON.parse(lat);
console.log(job.lon);
console.log(job.lat);
locations.geoNear([job.lon, job.lat], {spherical: true}, function(err, success){
if(success) {
res(success);
} else {
console.log('Response error' + err);
}
});
}
var AM = require('./modules/account-manager');
app.get('/geoFind', function(req, res) {
AM.geoNear('-73.99279', '40.719296', function(o){
res.send(200,o);
});
});
and I got the following result:
{
"waitedMS": 0,
"results": [
{
"dis": 1.4957325341976439e-7,
"obj": {
"_id": "581cc2f430c34502e36eb148",
"truckerID": "b233a9eaaedc63730e71a8b542606ee82e0aa5e5",
"name": "Justin",
"email": "Justin#gmail.com",
"company": "Justins Shipping",
"user": "justin1",
"pass": "D6Mvu6rUur758f37eac7010958c14557bb4df9871a",
"phone": "1234567890",
"location": [
-73.9928,
40.7193
]
}
},
{
"dis": 0.6482546796756842,
"obj": {
"_id": "581cc2f530c34502e36eb158",
"truckerID": "b233a9eaaedc63731e72a8b542606ee82e0aa7a6",
"name": "Alan",
"email": "Alan#gmail.com",
"company": "Alans Shipping",
"user": "alan1",
"pass": "D6Mvu6fUur758f37eac7010958c14557bb4df9872c",
"phone": "1234567890",
"location": [
-122.4194155,
37.7749295
]
}
}
],
"stats": {
"nscanned": 24,
"objectsLoaded": 2,
"avgDistance": 0.3241274146244688,
"maxDistance": 0.6482546796756842,
"time": 6
},
"ok": 1
}
I hope this answer helps someone else down the line who is also wondering why geoNear isn't returning any results.
EDIT: After doing even more research, it was actually because the collection needs to have a geospatial index created from the get-go. I tried adding the location field to all documents in the collection but still could not get any results back. Once I removed my collection entirely in mlab called "trucker" and re-added it, I was finally able to search that index and get results.
This line is needed initially:
trucker.createIndex( { location : "2dsphere" } )
Then you can use geoNear to find users near your location such as the following:
trucker.geoNear([job.lon, job.lat], {maxDistance:5000, distanceMultiplier: 6378137, spherical: true}, function(err, success){
if(success) {
res(success);
} else {
console.log('Response error' + err);
}
});

Mongoose: Update does not work in nested array object

I have a document with the array of objects and one object contains multiple objects I want to update inner object with $set but didn't get any luck.
can anybody give me any hint so that I can resolve it?.
This is my object:
{
"_id": ObjectId("56fbfafdf86fa6161911d104"),
"site": "xyz",
"adsPerCategory": NumberInt(2),
"sampledAt": ISODate("2016-03-30T16:12:45.138+0000"),
"items": [
{
"id": "4563873",
"content": {
"title": "WATER DISTILLERS",
"body": "Perfect to save money.",
}
},
{
"id": "4563s23232873",
"content": {
"title": "Cola water",
"body": "Perfect for body.",
}
}
]
}
I want to update body.
for now, I have given single object but it can be multiple.
Here what I tried
models.Sample.update(
{
_id: samples._id
},
'$set': {
'items.0.content.body': body.description
},
function(err, numAffected) {
console.log(err);
console.log('Affected....', numAffected);
}
);
It's working fine if I put 0 but I want to make it dynamic.
Like 'items.index.content.body': body.description
Thank You.
I think you can do something like this.
models.Sample.find({ _id: ObjectId(samples._id) })
.forEach(function (doc) {
doc.items.forEach(function (element, index, array) {
items[index].content.body = body.description;
});
models.Sample.save(doc);
});

Resources