MongoDb TypeError: Cannot read property 'insertMany' of undefined - node.js

import { MongoClient } from 'mongodb';
import assert from 'assert';
import config from './config';
console.log('Inside File');
MongoClient.connect(config.mongodbUri, (err, db) => {
assert.equal(null, err);
db.products.insertMany( [
{ item: "card", qty: 15 },
{ item: "envelope", qty: 20 },
{ item: "stamps" , qty: 30 }
] );
db.close();
});
For the above code i get the error Cannot read property insertMany of undefined.
But when i just enter the below
db.products.insertMany( [
{ item: "card", qty: 15 },
{ item: "envelope", qty: 20 },
{ item: "stamps" , qty: 30 }
] );
in the console the products seems to get inserted in the db just fine.
Can someone please let me know the cause of this issue?

Can try using db.collection('products').insertMany and callback function
MongoClient.connect(config.mongodbUri, (err, db) => {
assert.equal(null, err);
db.collection('products').insertMany( [
{ item: "card", qty: 15 },
{ item: "envelope", qty: 20 },
{ item: "stamps" , qty: 30 }
] , function(error, doc) {
if(error) {
console.log(error);
} else {
console.log('success');
}
db.close();
});
});

insertMany() is a mongoDB function, it will work if you try it in the console,
but in nodeJs you're dealing with Mongoose ( the mongoDb driver ) which doesn't have this function, to store items with mongoose, you need to do products.save() and it doesn't support bulk inserts ( you can only insert one at a time ) so you have to go through a loop :
var items = [{ item: "card", qty: 15 },
{ item: "envelope", qty: 20 },
{ item: "stamps" , qty: 30 }];
var Products = mongoose.model('Products'); // don't know what you have as a model
for ( var i in items) {
Products.save(items[i]);
}
of course you will need to handle errors ..etc.
if you don't already have a model here's a snippet:
var Schema = mongoose.Schema;
var products = new Schema({
item : String
, qty : int
});
mongoose.model("Products", Products);

Basically the function you're trying to use insertMany() is MongoDB function which can be directly used in the mongo console without any issues. But in case if you want to use it through your application that means using MongoClient library, you can do that too..
What you need to do is very simple, just use it the with the collection name you wanted to insert and passing the mandatory callback fn() as the other argument.
Syntax:
db.collection(<collectionName>).insertMany([{arglist1},{arglist2},
{arglistn..}],Callbackfn())
I Hope it'll be okay.

Related

Mongoose and Postman: test a model with nested objects

I created a model like this in nodeJS, using Mongoose:
'use strict';
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var plantSchema = new Schema({
plantData: [
{
family: { type: String, default: 'Liliaceae' },
genusObj: {
genus: { type: String, required: 'Please enter the genus plant name' },
tulipGroup: { type: String }, // e.g. Single Early
tulipGroupNumber: { type: Number } // e.g. 1
},
species: { type: String, required: 'Please enter the species plant name' },
commonName: { type: String },
description: { type: String },
mainImage: {},
otherImages: {},
images: {},
}
],
detailsData: [ .... ]
});
module.exports = mongoose.model('plants', plantSchema);
And this is my controller:
var mongoose = require('mongoose'),
Plant = mongoose.model('plants');
// READ ALL
exports.list_all_plants = function(req, res) {
Plant.find({}, function(err, plants) {
if (err) {
res.send(err);
}
res.json(plants);
});
};
// CREATE
exports.create_new_plant = function(req, res) {
var new_plant = new Plant(req.body);
new_plant.save(function(err, plant_inserted) {
if (err) {
res.send(err);
}
res.json(plant_inserted);
});
};
// READ (probably plantId comes from an _id previously retrieved)
exports.read_a_plant = function(req, res) {
Plant.findById(req.params.plantId, function(err, plant_searched) {
if (err) {
res.send(err);
}
res.json(plant_searched);
});
};
// UPDATE
exports.update_a_plant = function(req, res) {
Plant.findOneAndUpdate(
{
_id: req.params.plantId
},
req.body,
{new: true},
function(err, plant_to_update) {
if (err) {
res.send(err);
}
res.json(plant_to_update);
}
);
};
// DELETE
exports.delete_a_plant = function(req, res) {
Task.remove(
{
_id: req.params.plantId
},
function(err, task) {
if (err) {
res.send(err);
}
res.json({ message: 'Plant successfully deleted' });
}
);
};
And finally, i have this router:
'use strict';
module.exports = function(app) {
var plantList = require('../controllers/plantController');
// plant routes
app.route('/plants')
.get(plantList.list_all_plants)
.post(plantList.create_new_plant);
app.route('/plants/:plantId')
.get(plantList.read_a_plant)
.put(plantList.update_a_plant)
.delete(plantList.delete_a_plant);
What I'd like to do is testing all this with Postman.
If I try with the GET method, using simply
http://localhost:3000/plants
everything works fine: I mean, it returns an empty array (mongodb is up and running, and everything is set).
Now I wanted to try to insert a new element with Postman: I selected POST and x-www-form-urlencoded under body. Required properties are plantData{genusObj{genus}} and plantData{species} : since I'm quite new with both postman and mongodb, how can I enter a sub-element in postman, to create a new Plant ?
there are only KEY and VALUE options, and i don't know how to write a sub-key like plantData->genusObj->genus.
P.S.: Suggestions on data model are welcome, I tried to build a generic plant database but oriented on tulips (so usually i can enter tulips, but if i need to enter something else, i can).
Well, it seems that this answer fits to me: in fact, on Postman i selected under "body" the "raw" option, then I selected JSON instead of TEXT from the dropdown menu, and finally I used this object (meanwhile I slightly changed the
model) - don't forget the " symbols everywhere, like I did - ' is not accepted:
{
"plantData": [
{
"family": "Liliaceae",
"genusObj": {
"genus": "Tulipa",
"tulipGroup": "Single Late",
"tulipGroupNumber": 5
},
"species": "TEST",
"sellName": "Queen of night",
"description": "black tulip",
"mainImage": "",
"otherImages": "",
"images": ""
}
],
"sellingData": [
{
"price": 0.50,
"availableQuantity": 100
}
],
"detailsData": [
{
"heightInCm": "60-65",
"floweringTime": "late spring",
"plantDepthCm": "20",
"plantSpacingCm": "10",
"bulbSizeInCm": "12",
"flowerColor": "Black",
"lightRequirements": "full sun"
}
]
}

Cannot find id and update and increment sub-document - returns null Mongoose/mongoDB

I have a problem where I cannot seem to retrieve the _id of my nested objects in my array. Specifically the foods part of my object array. I want to find the _id, of lets say risotto, and then increment the orders count dynamically (from that same object).
I'm trying to get this done dynamically as I have tried the Risotto id in the req.body._id and thats fine but i can't go forward and try to increment orders as i get null.
I keep getting null for some reason and I think its a nested document but im not sure. heres my route file and schema too.
router.patch("/update", [auth], async (req, res) => {
const orderPlus = await MenuSchema.findByIdAndUpdate({ _id: '5e3b75f2a3d43821a0fb57f0' }, { $inc: { "food.0.orders": 1 }}, {new: true} );
//want to increment orders dynamically once id is found
//not sure how as its in its own seperate index in an array object
try {
res.status(200).send(orderPlus);
} catch (err) {
res.status(500).send(err);
}
});
Schema:
const FoodSchema = new Schema({
foodname: String,
orders: Number,
});
const MenuSchema = new Schema({
menuname: String,
menu_register: Number,
foods: [FoodSchema]
});
Heres the returned Database JSON
{
"_id": "5e3b75f2a3d43821a0fb57ee",
"menuname": "main course",
"menu_register": 49,
"foods": [
{
"_id": "5e3b75f2a3d43821a0fb57f0",
"foodname": "Risotto",
"orders": 37
},
{
"_id": "5e3b75f2a3d43821a0fb57ef",
"foodname": "Tiramisu",
"orders": 11
}
],
"__v": 0
}
the id for the menuname works in its place but i dont need that as i need to access the foods subdocs. thanks in advance.
You are sending food id (5e3b75f2a3d43821a0fb57f0) to the MenuSchema.findByIdAndUpdate update query. It should be the menu id which is 5e3b75f2a3d43821a0fb57ee
You can find a menu by it's id, and update it's one of the foods by using food _id or foodname using mongodb $ positional operator.
Update by giving menu id and food id:
router.patch("/update", [auth], async (req, res) => {
try {
const orderPlus = await MenuSchema.findByIdAndUpdate(
"5e3b75f2a3d43821a0fb57ee",
{ $inc: { "foods.$[inner].orders": 1 } },
{ arrayFilters: [{ "inner._id": "5e3b75f2a3d43821a0fb57f0" }], new: true }
);
res.status(200).send(orderPlus);
} catch (err) {
res.status(500).send(err);
}
});
Update by giving menu id and foodname:
router.patch("/update", [auth], async (req, res) => {
try {
const orderPlus = await MenuSchema.findByIdAndUpdate(
"5e3b75f2a3d43821a0fb57ee",
{ $inc: { "foods.$[inner].orders": 1 } },
{ arrayFilters: [{ "inner.foodname": "Risotto" }], new: true }
);
res.status(200).send(orderPlus);
} catch (err) {
res.status(500).send(err);
}
});

Express Route with /:id input returns empty array

I am trying to have my API take an id as input and return results from mongoDB according to the id given.
My example collection looks like this:
id: 1 {
count: 5
}
id: 2 {
count: 10
}
My mongoose Schemas looks like this:
var tripSchema = new Schema({
_id: Number,
count: Number
},
{collection: 'test'}
);
And I created another file for this route, where I think the error lies in:
module.exports = function(app) {
app.get('/trips/:id', function(req,res) {
console.log(req.params.id); // Does print the ID correctly
var aggr = Trip.aggregate([
{ "$match": {
"_id": {
"$eq": req.params.id
}
}
},
{
"$project": {
"_id" : 1,
"count": "$count"
}
}
])
aggr.options = { allowDiskUse: true };
aggr.exec(function(err, stations){
if(err)
res.send(err);
res.json(stations);
});
});
}
Now using postman I try to GET /trips/72, but this results in an empty array [], there is an entry in the DB for _id 72 with a corresponding count just like above. My question is if this is the correct approach and what I am doing wrong here.
--Update:
There seems to be something wrong with either the match stage or the whole aggregation. I opted for mongoose's findById, and with this it works now:
Trip.findById(req.params.id, function (err, doc){
res.json(doc);
});
req.params.id returns your id in String form, while I think in aggregate match section you need to pass it as ObjectId. So, you should convert it to ObjectId:
$match: { _id: ObjectId(req.params.id) }

Updating Reference Along With Other Values Mongoose

Here is a schema that I am working on.
var testSchema = mongoose.Schema({
userCreated : {
type : mongoose.Schema.Types.ObjectId,
ref : "User"
},
points : {type: Number, default: 0},
numVotes : {type: Number, default: 0},
createdAt : Date,
updatedAt : Date,
}, { timestamps : true });
Now, I am trying to write a function that will increment two fields on this document (points and numVotes, as well as an additional points field that exists on the user schema.
Here is my attempt.
testSchema.statics.incrementTest = function(id, ...) {
this.findByIdAndUpdate(id, {$inc : {
points : 5,
numVotes : 1,
'userCreated.points' : 5
}}).exec();
}
Now, this code that I have written does not work. However, when I comment out the 'userCreated.points' : 5 line, the other two fields do increment as expected. My question is, what is the best way using mongoose to update the fields on a document and the fields on a subdocument at the same time?
The data here is contained in different collections, so no single update statement is able to increment counters in both at the same time.
In order to get a consistent view you are going to need to "chain" your update statements and use the return results of each to build the response.
Depending on your needs you can either use a Promise with this:
testSchema.statics.incrementTest = function(id) {
var self = this;
return new Promise(function(resolve,reject) {
self.findByIdAndUpdate(
id,
{
"$inc": {
"points": 5,
"numVotes": 1
}
},
{ "new": true }
).then(function(test) {
var userModel = test.schema.path("userCreated").options.ref;
mongoose.model(userModel).findByIdAndUpdate(
test.userCreated,
{ "$inc": { "points": 5 } },
{ "new": true }
).then(function(user) {
test.userCreated = user;
resolve(test);
})
}).catch(reject)
})
};
Which you can then invoke on your model:
Test.incrementTest("56fe279d363ce91765d9e39e").then(function(test) {
console.log(JSON.stringify(test,undefined,2));
}).catch(function(err) {
throw err;
})
Or you can use async.waterfall from the async library if that suits you better:
testSchema.statics.incrementTest = function(id,callback) {
var self = this;
async.waterfall(
[
function(callback) {
self.findByIdAndUpdate(
id,
{
"$inc": {
"points": 5,
"numVotes": 1
}
},
{ "new": true },
callback
)
},
function(err,test) {
if (err) callback(err);
var userModel = test.schema.path("userCreated").options.ref;
mongoose.model(userModel).findByIdAndUpdate(
test.userCreated,
{ "$inc": { "points": 5 } },
{ "new": true },
function(err,user) {
if ( typeof(user) !== "undefined" )
test.userCreated = user;
callback(err,test);
}
);
}
],
callback
);
};
Which has a similar usage:
Test.incrementTest("56fe279d363ce91765d9e39e",function(err,test) {
if (err) throw err;
console.log(JSON.stringify(test,undefined,2));
})
Both should be giving you a result back that shows the incremented data in both objects for both collections:
{ points: 5,
numVotes: 1,
__v: 0,
userCreated: { points: 5, __v: 0, _id: 56ff1aa6dba6d13e798fc894 },
createdAt: Sat Apr 02 2016 12:04:38 GMT+1100 (AEDT),
updatedAt: Sat Apr 02 2016 12:04:38 GMT+1100 (AEDT),
_id: 56fe279d363ce91765d9e39e }

How to use populate functionality by using populate or making inner query with aggregation in mongodb

I have following data in my Mongodb.
{
"_id" : ObjectId("54a0d4c5bffabd6a179834eb"),
"is_afternoon_scheduled" : true,
"employee_id" : ObjectId("546f0a06c7555ae310ae925a")
}
I would like to use populate with aggregate, and want to fetch employee complete information in the same response, I need help in this. My code is:
var mongoose = require("mongoose");
var empid = mongoose.Types.ObjectId("54a0d4c5bffabd6a179834eb");
Availability.aggregate()
.match( { employee_id : empid} )
.group({_id : "$employee_id",count: { $sum: 1 }})
.exec(function (err, response) {
if (err) console.log(err);
res.json({"message": "success", "data": response, "status_code": "200"});
}
);
The response i am getting is
{"message":"success","data":{"_id":"54a0d4c5bffabd6a179834eb","count":1},"status_code":"200"}
My expected response is:
{"message":"success","data":[{"_id":"54aa34fb09dc5a54232e44b0","count":1, "employee":{fname:abc,lname:abcl}}],"status_code":"200"}
You can call the model form of .populate() on the result objects from an aggregate operation. But the thing is you are going to need a model to represent the "Result" object returned by your aggregation in order to do so.
There are a couple of steps, best explained with a complete listing:
var async = require('async'),
mongoose = require('mongoose'),
Schema = mongoose.Schema;
var employeeSchema = new Schema({
"fname": String,
"lname": String
})
var availSchema = new Schema({
"is_afternoon_scheduled": Boolean,
"employee_id": {
"type": Schema.Types.ObjectId,
"ref": "Employee"
}
});
var resultSchema = new Schema({
"_id": {
"type": Schema.Types.ObjectId,
"ref": "Employee"
},
"count": Number
});
var Employee = mongoose.model( "Employee", employeeSchema );
var Availability = mongoose.model( "Availability", availSchema );
var Result = mongoose.model( "Result", resultSchema, null );
mongoose.connect('mongodb://localhost/aggtest');
async.series(
[
function(callback) {
async.each([Employee,Availability],function(model,callback) {
model.remove({},function(err,count) {
console.log( count );
callback(err);
});
},callback);
},
function(callback) {
async.waterfall(
[
function(callback) {
var employee = new Employee({
"fname": "abc",
"lname": "xyz"
});
employee.save(function(err,employee) {
console.log(employee),
callback(err,employee);
});
},
function(employee,callback) {
var avail = new Availability({
"is_afternoon_scheduled": true,
"employee_id": employee
});
avail.save(function(err,avail) {
console.log(avail);
callback(err);
});
}
],
callback
);
},
function(callback) {
Availability.aggregate(
[
{ "$group": {
"_id": "$employee_id",
"count": { "$sum": 1 }
}}
],
function(err,results) {
results = results.map(function(result) {
return new Result( result );
});
Employee.populate(results,{ "path": "_id" },function(err,results) {
console.log(results);
callback(err);
});
}
);
}
],
function(err,result) {
if (err) throw err;
mongoose.disconnect();
}
);
That's the complete example, but taking a closer look at what happens inside the aggregate result is the main point:
function(err,results) {
results = results.map(function(result) {
return new Result( result );
});
Employee.populate(results,{ "path": "_id" },function(err,results) {
console.log(results);
callback(err);
});
}
The first thing to be aware of is that the results returned by .aggregate() are not mongoose documents as they would be in a .find() query. This is because aggregation pipelines typically alter the document in results from what the original schema looked like. Since it is just a raw object, each element is re-cast as a mongoose document for the Result model type defined earlier.
Now in order to .populate() with data from Employee, the model form of this method is called on the array of results in document object form along with the "path" argument to the field to be populated.
The end result fills is the data as it comes from the Employee model it was related to.
[ { _id:
{ _id: 54ab2e3328f21063640cf446,
fname: 'abc',
lname: 'xyz',
__v: 0 },
count: 1 } ]
Different to how you process with find, but it is necessary to "re-cast" and manually call in this way due to how the results are returned.
This is working like applied populate with aggregate using inner query.
var mongoose = require("mongoose");
var empid = mongoose.Types.ObjectId("54a0d4c5bffabd6a179834eb");
Availability.aggregate()
.match( { employee_id : empid} )
.group({_id : "$employee_id",count: { $sum: 1 }})
.exec(function (err, response) {
if (err) console.log(err);
if (response.length) {
var x = 0;
for (var i=0; i< response.length; i++) {
empID = response[i]._id;
if (x === response.length -1 ) {
User.find({_id: empID}, function(err, users){
res.json({"message": "success", "data": users, "status_code": "200"});
});
}
x++;
}
}
}
);

Resources