How to get result to array in mongoose? - node.js

I’m new to angular and I just started dealing with mongodb using mongoose.
In my application I need to get name list of drivers as string array, but, I’m getting array of objects with object id.
{
"_id": "5aa90ab23c49a72488afab7a",
"name": "mr. Rusiru ekanayaka"
},
{
"_id": "5aa90d4ba8c6b35438a8b132",
"name": "mr. Gihan ekanayaka"
},
{
"_id": "5aa90d56a8c6b35438a8b133",
"name": "mr. Gihan ekanayaka"
}
}
But I need something like this,
[
' mr. Gihan ekanayaka,
' mr. rusiru ekanayaka ',
' mr. Gihan ekanayaka '
]
I think I can re-format this in my backend by looping through the object. By is there anyway to get only one field of document in collection as string array without object id?
In my Driver model I do like this.
module.exports.getDrivers = function(callback){
Driver.find({},'name',callback);
}

Try this
module.exports.getDrivers = function(callback){
Driver.find({},{'_id' : 0,'name' : 1},callback);
}

Try this:
module.exports.getDrivers = function(callback){
Driver.distinct('name', callback);
}

One way to do it is to use a map, for example like this:
Driver.find({}).map(driver => driver.name);
That (with adjustments to the code to fit your coding environment) will return an array of driver.name items, like this:
[
"mr. Rusiru ekanayaka",
"mr. Gihan ekanayaka",
"mr. Gihan ekanayaka"
]

Related

MongoDB schema: store id as FK or whole document

I am designing MongoDB structure (the models structure in NodeJS app actually). I will have players and matches collections.
Is it better to store only the ids of the matches the player joined,inside each player's object (like a FK in RDBM) or store the whole object of match inside the player object?
In the application one of the action would be to show the details of the match and on this view the user will see the players that joined this particular match (their names, country etc.). That makes me think that storing whole Match document inside the Player document is better.
Any tips?
Storing whole Match document inside the Player document is not a good option I think.
Your player document will need to be updated every time the player play in a match.
You have 2 main alternatives:
1-) Using child referencing. (referencing player in match).
So if we want to imlement this using mongoose models:
Player model:
const mongoose = require("mongoose");
const playerSchema = mongoose.Schema({
name: String,
country: String
});
const Player = mongoose.model("Player", playerSchema);
module.exports = Player;
Match model:
const mongoose = require("mongoose");
const matchSchema = mongoose.Schema({
date: {
type: Date,
default: Date.now()
},
players: [
{
type: mongoose.Schema.Types.ObjectId,
ref: "Player"
}
]
});
const Match = mongoose.model("Match", matchSchema);
module.exports = Match;
With these models, our match document will be like this (referencing playerId's):
{
"_id" : ObjectId("5dc419eff6ba790f4404fd07"),
"date" : ISODate("2019-11-07T16:19:39.691+03:00"),
"players" : [
ObjectId("5dc41836985aaa22c0c4d423"),
ObjectId("5dc41847985aaa22c0c4d424"),
ObjectId("5dc4184e985aaa22c0c4d425")
],
"__v" : 0
}
And we can use this route to get match info with all players info:
const Match = require("../models/match");
router.get("/match/:id", async (req, res) => {
const match = await Match.findById(req.params.id).populate("players");
res.send(match);
});
And the result will be like this:
[
{
"date": "2019-11-07T13:19:39.691Z",
"players": [
{
"_id": "5dc41836985aaa22c0c4d423",
"name": "player 1",
"country": "country 1",
"__v": 0
},
{
"_id": "5dc41847985aaa22c0c4d424",
"name": "player 2",
"country": "country 1",
"__v": 0
},
{
"_id": "5dc4184e985aaa22c0c4d425",
"name": "player 3",
"country": "country 2",
"__v": 0
}
],
"_id": "5dc419eff6ba790f4404fd07",
"__v": 0
}
]
2-) Embedding players inside match, and still keeping a independent players collection.
But this will need more space than first option.
So your a match will look like this in matches collection:
{
"date": "2019-11-07T13:19:39.691Z",
"players": [
{
"_id": "5dc41836985aaa22c0c4d423",
"name": "player 1",
"country": "country 1",
"__v": 0
},
{
"_id": "5dc41847985aaa22c0c4d424",
"name": "player 2",
"country": "country 1",
"__v": 0
},
{
"_id": "5dc4184e985aaa22c0c4d425",
"name": "player 3",
"country": "country 2",
"__v": 0
}
],
"_id": "5dc419eff6ba790f4404fd07",
"__v": 0
}
But this may be a little faster when getting a match info, since there is no need to populate players info.
const Match = require("../models/match");
router.get("/match/:id", async (req, res) => {
const match = await Match.findById(req.params.id);
res.send(match);
});
The way I see it, the matches collection here is a collection of documents that exists independently and then connected with the players that participates to the matches. With that said, I would do an array of match keys.
I would suggest going for a nested document structure if the document being nested can be considered as "owned" by the parent document. For example, a todo nested document inside of a todoList document.
This is a case of many-to-many relationship.
I am guessing that there will be about 100 players and 100 matches data, initially.
The design options are embedding or referencing.
(1) Embedding:
The most queried side will have the less queried side embedded.
Based on your requirement (show the details of the match and on this view the user will see the players that joined this particular match and their details) the match side will have the player data embedded.
The result is two collections.The main one is the matches. The secondary is the players; this will have all the source data for a player (id, name, dob, country, and other details).
Only a few players data is stored for a match, and only a subset of a player data is stored in the matches collection.
Results in duplication of player data. This is fine, it is mostly static info that will be duplicated; things like name and country. But, some of it may need updates over time and the application needs to take care of this.
The player data is stored as an array of embedded documents in the matches collection. This design is the possible solution.
matches:
_id
matchId
date
place
players [ { playerId 1, name1, country1 }, { playerId 2, ... }, ... ]
outcome
players:
_id
name
dob
country
ranking
(2) Referencing:
This will also have two collections: players and matches.
Referencing can happen with either side, the matches can refer the players or vice-versa.
Based on the requirement, the most queried side will have references of the less queried side; matches will have the player id references. This will be an array of player ids.
matches:
_id
matchId
date
place
players [ playerId 1, playerId 2, ... ]
The players collection will have the same data as in earlier case.

Does MongoDB have a way to update a document without dropping existing elements not contained in the update data

I have been using MongoDB with both .NET core and Node.js. This question is specific to a node project I am working on, but I think the client language is irrelevant to my questions.
I have a document that has elements like name and address current ("add_current") below:
{
"_id" : ObjectId("5d858718c1d9f856881dc9ce"),
...
"name": "John Doe",
"add_current" :
{
"add1" : "456 Old Ave.",
"city" : "Stafford",
"state" : "VA",
"zip" : "23234"
},
...
}
Sometimes I am just updating some parts of the child object like this:
const updateDocument2 = function (db, callback) {
const collection = db.collection('documents');
collection.update(
{
'_id': ObjectID("5d858718c1d9f856881dc9ce")
},
{
$set: {
name: "John Doe Jr.",
add_current: {
add1: "123 New Street",
}
}
}, function (err, result) {
console.log("Updated the document");
callback(result);
});
}
When I execute the $set above, I delete the fields city, state and zip. I don't want to do that.
I am seeking the most efficient way to update name and add_current.add1 without deleting other fields (like add_current.state). I realize that there are ways to do this with multiple touches to the data record (.findOne(...) then .update(...)). Is there a way to do it with a single .update(...) touch?
you are setting add_current's value to {add1: "123 New Street"}
try {$set:{"add_current.add1": "123 New Street"}}

Node.js/MongoDB - querying dates

I'm having a bit of an issue understanding how to query dates; I think the issue might be with how my data is structured. Here is a sample document on my database.
{
"phone_num": 12553,
"facilities": [
"flat-screen",
"parking"
],
"surroundings": [
"ping-pong",
"pool"
],
"rooms": [
{
"room_name": "Standard Suite",
"capacity": 2,
"bed_num": 1,
"price": 50,
"floor": 1,
"reservations": [
{
"checkIn": {
"$date": "2019-01-10T23:23:50.000Z"
},
"checkOut": {
"$date": "2019-01-20T23:23:50.000Z"
}
}
]
}
]
}
I'm trying to query the dates to see check if a specific room is available at a certain date-range but no matter what I do I can't seem to get a proper result, either my query 404's or returns empty array.
I really tried everything, right now for simplicity I'm just trying to get the query to work with checkIn so I can figure out what I'm doing wrong. I tried 100 variants of the code below but I couldn't get it to work at all.
.find({"rooms": { "reservations": { "checkIn" : {"$gte": { "$date": "2019-01-09T00:00:00.000Z"}}}}})
Am I misunderstanding how the .find method works or is something wrong with how I'm storing my dates? (I keep seeing people mentioning ISODates but not too sure what that is or how to implement).
Thanks in advance.
I think the query you posted is not correct. For example, if you want to query for the rooms with the checkin times in a certain range then the query should be like this -
.find({"rooms.reservations.checkout":{$gte:new Date("2019-01-06T13:11:50+06:00"), $lt:new Date("2019-01-06T14:12:50+06:00")}})
Now you can do the same with the checkout time to get the proper filtering to find the rooms available within a date range.
A word of advice though, the way you've designed your collection is not sustainable in the long run. For example, the date query you're trying to run will give you the correct documents, but not the rooms inside each document that satisfy your date range. You'll have to do it yourself on the server side (assuming you're not using aggregation). This will block your server from handling other pending requests which is not desirable. I suggest you break the collection down and have rooms and reservations in separate collections for easier querying.
Recently I was working on date query. First of all we need to understand how we store date into the mongodb database. Say I have stored data using UTC time format like 2020-07-21T09:45:06.567Z.
and my json structure is
[
{
"dateOut": "2020-07-21T09:45:06.567Z",
"_id": "5f1416378210c50bddd093b9",
"customer": {
"isGold": true,
"_id": "5f0c1e0d1688c60b95360565",
"name": "pavel_1",
"phone": 123456789
},
"movie": {
"_id": "5f0e15412065a90fac22309a",
"title": "hello world",
"dailyRentalRate": 20
}
}
]
and I want to perform a query so that I can get all data only for this( 2020-07-21) date. So how can we perform that?. Now we need to understand the basic.
let result = await Rental.find({
dateOut: {
$lt:''+new Date('2020-07-22').toISOString(),
$gt:''+new Date('2020-07-21').toISOString()
}
})
We need to find 21 date's data so our query will be greater than 21 and less than 22 cause 2020-07-21T00:45:06.567Z , 2020-07-21T01:45:06.567Z .. ... .. this times are greater than 21 but less than 22.
var mydate1 = new Date();
var mydate1 = new Date().getTime();
ObjectId.getTimestamp()
Returns the timestamp portion of the ObjectId() as a Date.
Example
The following example calls the getTimestamp() method on an ObjectId():
ObjectId("507c7f79bcf86cd7994f6c0e").getTimestamp()
This will return the following output:
ISODate("2012-10-15T21:26:17Z")
If your using timestamps data to query.
EG : "createdAt" : "2021-07-12T16:06:34.949Z"
const start = req.params.id; //2021-07-12
const data = await Model.find({
"createdAt": {
'$gte': `${start}T00:00:00.000Z`,
'$lt': `${start}T23:59:59.999Z`
}
});
console.log(data);
it will show the data of particular date .i.,e in this case. "2021-07-12"

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.

Combine Mongo Output with Node for API

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.

Resources