Combine Mongo Output with Node for API - node.js

I''m really new to Node but I currently have a NodeJS / Express open source CMS and would like to output some API data for an app that I am working. Forgive me if I'm not using the correct terminology or whatnot, this is new to me.
What I currently have are two collections, locations and tours. The CMS allows me to create a relationship between the two. This simply stores an array of ObjectID's in the locations record for each associated tour record.
What I want to do is take my API output code (below) and have it output the entire tours array, complete with all the fields (title, description, etc), in with each location record. Currently it only outputs an array of the ID's.
Here is my current code:
var async = require('async'),
landmark = require('keystone');
var Location = keystone.list('Location'),
Tour = keystone.list('Tour');
/**
* List Locations
*/
exports.list = function(req, res) {
Location.model.find(function(err, items) {
if (err) return res.apiError('database error', err);
res.apiResponse({
locations: items
});
});
}
/**
* Get Location by ID
*/
exports.get = function(req, res) {
Location.model.findById(req.params.id).exec(function(err, item) {
if (err) return res.apiError('database error', err);
if (!item) return res.apiError('not found');
res.apiResponse({
location: item
});
});
}
Current API output (truncated):
{
"locations": [
{
"_id": "53a47997ebe91d8a4a26d251",
"slug": "test-location",
"lastModified": "2014-06-20T20:19:14.484Z",
"commonName": "test location",
"__v": 3,
"url": "",
"tours": [
"53a47963ebe91d8a4a26d250"
],
"images": []
}
]
}
What I'm looking for:
{
"locations": [
{
"_id": "53a47997ebe91d8a4a26d251",
"slug": "test-location",
"lastModified": "2014-06-20T20:19:14.484Z",
"commonName": "test location",
"__v": 3,
"url": "",
"tours": [
{
"_id": "53a47963ebe91d8a4a26d250",
"title": "my test tour title",
"url": "url_to_audio_file"
}
],
"images": []
}
]
}
Anyone know if this is possible? Any help would be appreciated! Thanks!

It looks like you have setup your Location model to have a reference to the Tours, defined as an array of Tours. This means that when you store the Tour within your Location, you're not storing the data that represents that Tour, but instead an ID that references the Tour. When you perform the find operation, you're seeing that in the response that you send back to the client.
If this is the case, then you might want to take a look at Mongoose's populate function. This will take those references and populate them fully with the data that they contain.
So for instance, you can change your query to the following:
Location.model.find().populate('tours').exec(function(err, items) {
// items should now contain fully populated tours
}
Let me know if this isn't what you mean and I can try to help further.

The solution provided by #dylants is absolutely correct. However, for it to work you need to have tours declared as a Types.Relationship field in your Location list with the ref option set to Tour.
Check out the Keystone docs on Relationship Fields.
I included the many: true option in my example below, because I assumed this is a one-to-many relationship. If it isn't, you can discard it.
var keystone = require('keystone'),
Location = keystone.list('Location');
Location.add({
...
tours: { type: Types.Relationship, ref: 'Tour', many: true },
...
});
The List.relationship() method you mentioned is meant to be used only if you want a list of related documents to automatically appear in the Keystone Admin UI, and not to establish the actual relationship.
Hope this helps.

Related

How to return an entire array (NO FILTER!!!!!), from mongodb using node.js

heres my mongodb group document. As you can see i have an _id, which i use to find the group itself, an owner, an array of admins and an array of members.
{
"_id": {
"$oid": "60c7246f61a6cc7527f815d2"
},
"groupName": "soogroo1",
"creationDate": "11/06/2020",
"premiumStatus": true,
"phone": "08741536329995757575757575757575577575757575",
"profilePic": "post-5f2a01e2-efe3-4fa0-8302-76bfd2d70b4b-1622806860268",
"owner": ["9b8bcd57-06eb-471c-8910-c5b944d02431"],
"admin": ["f2171431-627e-47a3-a65f-4abf48d361b6", "5e3df015-a1ed-4a63-a16e-83458d0e7da3", "f85baa4a-1015-4a5e-b1ed-b79001a9f277"],
"member": ["6b1233b2-098e-480b-9462-c010c8b8de06", "0bcbb92d-6276-4118-8576-9d5f5c4ed43b"]
}
essentially i have searched the entirety of the world wide web looking for one of possibly the simplest most fundamental thing i can think of, how on gods green earth do you query an array of strings, and return the entire array. All i want to do is pass in a group id, specify that i want the entire member field returned, and then ill be able to map the members to a list on the front-end like you would with following/followers on instagram for example.
Everywhere i look all i can find is people who use arrays of objects and people who filter arrays and all that bs, i just want the entire array string for string returned. Please help i'm pulling my hair out lol.
BTW im using nodeJS with express and reactJS on the front-end (not that that's relevant)
vvvvv RELEVANT CODE vvvvv
folder name : routes
file name : groups.js
app.get("/groups/:groupId/members", (req, res)=>{
groups.getGroupsMembers(req.params.groupId).then(data=>{
res.send(data)
})
})
folder name : main
file name : groups.js
exports.getGroupsMembers = (groupId) => {
return myMongo.getGroupsMemberList("groups", groupId);
};
folder name : main
file name : mongo.js
vvvvvv (the part that is broken) vvvvvv
exports.getGroupsMemberList = (collection, groupId) => {
return db.collection(collection).findOne(
{ _id: ObjectID(groupId)},
).members
}
I currently have no way to test your code, but I guess your problem is because you are extending query with .members, not actually getting members attribute from the return value of query.
exports.getGroupsMemberList = async (collection, groupId) => {
return await (db.collection(collection).findOne(
{ _id: ObjectID(groupId)},
)).members
}
I am not sure if mongo driver accepts async/await, so you might need to do this through callbacks..?

Editing/Updating nested objects in documents CouchDB (node.js)

I'm trying to add (aka. push to existing array) in couchDB document.
Any feedback is greatly appreciated.
I have a document called "survey" inside my database called "database1".
I have "surveys" as a set of arrays which consists of objects that has information on each survey.
My goal is to update my "survey" document. Not replacing my array, but adding a new object to the existing array. I've used "nano-couchdb" and "node-couchdb", but could not find a way around it. I was able to update my "surveys", but it would replace the whole thing, not keeping the existing objects in array.
1) Using Nano-couchdb:
db.insert({ _id, name }, "survey", function (error, resp) {
if(!error) { console.log("it worked")
} else {
console.log("sad panda")}
})
2) Using couchdb-node:
couch.update("database1", {
_id: "survey",
_rev:"2-29b3a6b2c3a032ed7d02261d9913737f",
surveys: { _id: name name: name }
)
These work well with adding new documents to a database, but doesn't work with adding stuff to existing documents.
{
"_id": "survey",
"_rev": "2-29b3a6b2c3a032ed7d02261d9913737f",
"surveys": [
{
"_id": "1",
"name": "Chris"
},
{
"_id": "2",
"name": "Bob"
},
{
"_id": "1",
"name": "Nick"
}
]
}
I want my request to work as it would for
"surveys.push({_id:"4",name:"harris"})
whenever new data comes in to this document.
Your data model should be improved. In CouchDB it doesn't make much sense to create a huge "surveys" document, but instead store each survey as a separate document. If you need all surveys, just create a view for this. If you use CouchDB 2.0, you can also query for survey documents via Mango.
Your documents could look like this:
{
"_id": "survey.1",
"type": "survey",
"name": "Chris"
}
And your map function would look like that:
function (doc) {
if (doc.type === 'survey') emit(doc._id);
}
Assuming you saved this view as 'surveys' in the design doc '_design/documentLists', you can query it via http://localhost:5984/database1/_design/documentLists/_view/surveys.

A find() statement with possible null parameters

I'm trying to figure out how Mongoose and MongoDB works... I'm really new to them, and I can't seem to figure how to return values based on a find statement, where some of the given parameters in the query possible are null - is there an attribute I can set for this or something?
To explain it further, I have a web page that has different input fields that are used to search for a company, however they're not all mandatory.
var Company = mongoose.model('Company');
Company.find({companyName: req.query.companyName, position: req.query.position,
areaOfExpertise: req.query.areaOfExpertise, zip: req.query.zip,
country: req.query.country}, function(err, docs) {
res.json(docs);
});
By filling out all the input fields on the webpage I get a result back, but only that specific one which matches. Let's say I only fill out country, it returns nothing because the rest are empty, but I wish to return all rows which are e.g. in Germany. I hope I expressed myself clearly enough.
You need to wrap the queries with the $or logic operator, for example
var Company = mongoose.model('Company');
Company.find(
{
"$or": [
{ "companyName": req.query.companyName },
{ "position": req.query.position },
{ "areaOfExpertise": req.query.areaOfExpertise },
{ "zip": req.query.zip },
{ "country": req.query.country }
]
}, function(err, docs) {
res.json(docs);
}
);
Another approach would be to construct a query that checks for empty parameters, if they are not null then include it as part of the query. For example, you can just use the req.query object as your query assuming the keys are the same as your document's field, as in the following:
/*
the req.query object will only have two parameters/keys e.g.
req.query = {
position: "Developer",
country: "France"
}
*/
var Company = mongoose.model('Company');
Company.find(req.query, function(err, docs) {
if (err) throw err;
res.json(docs);
});
In the above, the req.query object acts as the query and has an implicit logical AND operation since MongoDB provides an implicit AND operation when specifying a comma separated list of expressions. Using an explicit AND with the $and operator is necessary when the same field or operator has to be specified in multiple expressions.
If you are after a query that satisfies both logical AND and OR i.e. return all documents that match the conditions of both clauses for example given a query for position AND country OR any other fields then you may tweak the query to:
var Company = mongoose.model('Company');
Company.find(
{
"$or": [
{ "companyName": req.query.companyName },
{
"position": req.query.position,
"country": req.query.country
},
{ "areaOfExpertise": req.query.areaOfExpertise },
{ "zip": req.query.zip }
]
}, function(err, docs) {
res.json(docs);
}
);
but then again this could be subject to what query parameters need to be joined as mandatory etc.
I simply ended up deleting the parameters in the query in case they were empty. It seems all the text fields in the submit are submitted as "" (empty). Since there are no such values in the database, it would return nothing. So simple it never crossed my mind...
Example:
if (req.query.companyName == "") {
delete req.query.companyName;
}

one way direction, sails and mongodb

I have a question. I think i am doing something wrong.
I have two models:
tutorType and Student.
Here is my Student
module.exports = {
attributes: {
tutor1Name:'string',
tutor1Type: {
model: 'tutorType'
},
contact: {
type: 'numeric'
},
}
};
And here is my TutorType
module.exports = {
attributes: {
libelle: "string"
}
};
I use the predefined blue print to insert. When i Insert a new student
I have the returned response:
{
"name": "Luke",
"tutor1Type": "Biological",
"createdAt": "2015-07-13T17:57:12.526Z",
"updatedAt": "2015-07-13T17:57:12.526Z",
"id": "55a3fbf8c9e93bf0266a63a8"
}
Should tutor1Type be an object instead of a String? Actually, I can put the string I want. I would like to be able to put only rows i have in TutorType with a foreign key on my Student.
What wrong I do?!
EDIT
Just add a look in my DB.
When I send the ID of the tutorType to my controller. It add an "Object(hashID)" in db. I presume that is a good news.
The things is that I can also insert string that are not into my tutorList...
I do not understand how integrity work right here...
Any ideas?!

How do I sort 'auto-magically' using Mongoose?

Say I have a URL like this:
http://dev.myserver.com/systems?system_type.category=Penetration
which hits the following controller:
exports.findAll = function(req, res) {
System.find(req.query, function(err, systems) {
res.send(systems);
});
};
and then returns the set below 'auto-magically' using Node, Express, MongoDB and Mongoose:
[{
"_id": "529e5f29c128685d860b3bad",
"system_number": "123",
"target_country": "USA",
"system_type": {
"category": "Penetration",
"subcategory": ["Floor", "Wall"]
}
},{
"_id": "999e5f29c128685d860b3bad",
"system_number": "456",
"target_country": "Canada",
"system_type": {
"category": "Penetration",
"subcategory": ["Floor", "Wall"]
}
}]
Now, if I want the results sorted by 'target_country', what is the 'best practice' for 'auto-magically' doing that?
Are there certain parameters/syntax that Mongoose/Express are expecting to do it for me? Or, is this a case where I have to specifically code for it? (That would kill the 'auto-magical' functionality already there.)
Thanks!
UPDATE: Here is what worked for me.
exports.findAll = function(req, res) {
// Sort Ascending:
http://dev.dom.com/systems?system_type.category=Penetration&sort=system_number
// Sort Descending:
http://dev.dom.com/systems?system_type.category=Penetration&sort=-system_number
// Default sort ascending on system_number:
http://dev.dom.com/systems?system_type.category=Penetration
var sort_param = req.query.sort ? req.query.sort : 'system_number';
System.find().sort(sort_param).find(function(err, menus) {
res.send(menus);
});
};
I guess where I went wrong, was to think I should find with filters and then sort, instead of find all, sort and then find again with filters. Still getting my head wrapped around the whole 'callback philosophy' I guess.
You need to define separate URL parameters for the query and sort components of your System query. As in:
System.find(req.query.query).sort(req.query.sort).exec(function(err, systems) {
res.send(systems);
});
Then you'd use request URL parameters that look like:
?sort=target_country
&query[system_type.category]=Penetration
&query[system_type.subcategory]=Floor
Docs on sort here.

Resources