MongoDB with Node Index Error - javascript

I'm currently using a node.js application to scan Twitter's API based on a set of parameters, and then uploading those JSON objects to a MongoDB database kept on MLab. I have connected to the database without issue, but my code will only upload ONE tweet before crashing. Here is the error message:
(node:62948) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 2): BulkWriteError: E11000 duplicate key error index: test-database.test-collection.$_id_ dup key: { : ObjectId('5aecb49e205197f5e4f52e32') }
It seems to have something to do with the keys that I am using in the database? How can I write my code so that I don't have this issue. Here is my program right now:
var Twitter = require("twitter");
var config = require("./config");
const mongoose = require("mongoose");
const MongoClient = require("mongodb");
var twitterClient = new Twitter(config);
const assert = require("assert");
const dbName = "test-database";
const collectionName = "test-collection";
const url = "mongodb://user:password#ds113870.mlab.com:13870/test-database";
const param = {follow: '21111098,958191744683782144,18061669,21111098,18061669,2891210047,1869975300,19394188,4107251,16056306,259459455,21111098,18061669,2891210047,1869975300,19394188,4107251,16056306,259459455,968650362,343041182,5558312,111671288,476256944,378631423,803694179079458816,30354991,224285242,45645232,235217558,20879626,150078976,278124059,102477372,249787913,381577682,15324851,435500714,823302838524739584,20597460,555355209,15745368,229966028,3001665106,2863210809,1397501864,78403308,253252536,47747074,1262099252,1284467173,92186819,169198625,600463589,413160266,1096059529,1095504170,1058520120,328679423,247334603,308794407,216503958,234128524,59969802,10615232,118740781,1383059977,2856787757,75364211,586730005,18632666,18632809,1249982359,339822881,365530059,216881337,3229124078,55677432,816683274076614656,26594419,1068481578,1068540380,19726613,13529632,18137749,3067974778,109071031,278094476,21406834,1129029661,970207298,357606935,236511574,145292853,76456274,456137574,33537967,941000686275387392,555474658,264219447,11650762,16160352,57065141,753693622692970497,21269970,238177562,389554914,11651202,214767677,515822213,16473577,1071402577,323490669,1480852568,2962923040,2987970190,811313565760163844,3145735852,266133081,41363507,109287731,14125897,946549322,361569788,15808765,1603426344,18695134,407039290,1099199839,183062944,60828944,325231436,14140370,17494010,1872999342,72198806,709389393811927041,21157904,213339899,2964174789,22195441,1061029050,460376288,382791093,106733567,43910797,24768753,18915145,240790556,2612307559,7270292,20546536,225921757,27044466,250188760,292495654,122124607,29201047,223166587,171598736,94154021,221162525,26062385,486694111,242555999,770121222,14845376,432895323,3219708271,217543151,81191343,2955485182,978029858,296361085,26533227,76649729,21669223,283130017,73303753,13218102,1648117711,1074480192,23022687,262756641,18170310,88784440,242836537,946946130,172858784,7429102,409719505,293131808,158470209,117501995,35567751,193794406,158890005,234374703,113355380,1074518754,87510313,233737858,291756142,1848942470,202206694,499268312'};
let newTweet = {
name: "",
text: "",
followers: ""
}
MongoClient.connect(url, function(err, client){
assert.equal(null,err);
console.log("connected.");
const db = client.db(dbName);
const collection = db.collection(collectionName);
const insertDocument = function(db, callback){
// THIS IS WHERE I THINK THE PROBLEM IS //
collection.insert(newTweet)
}
twitterClient.stream('statuses/filter',param,function(stream) {
stream.on('data', function(tweet) {
newTweet.name = tweet.user.screen_name;
newTweet.followers = tweet.user.followers_count;
newTweet.text = (tweet.extended_tweet) ? tweet.extended_tweet.text : tweet.text;
insertDocument(newTweet, function(){
db.close();
});
});
});
});

You are closing the database immediately after inserting one tweet.
// stream.on('data', function(tweet) {
insertDocument(newTweet, function(){
db.close();
});
Instead, close the connection on stream end.
stream.on('end', function() {
db.close();
});
You are getting the Duplicate key issue, because you declared the newTweet object globally, which shares the same object for every tweet. Declare the tweet object inside the stream.on('data') handler function. i.e.
stream.on('data', function(tweet) {
let newTweet = {};
newTweet.name = tweet.user.screen_name;
newTweet.followers = tweet.user.followers_count;

Related

MongoDB: Error “Cannot use a session that has ended” when inserting into a collection

Started using mongoDB today and used the sample code to first connect to a existing database and then add a new object into my collection:
//jshint esversion:6
const { MongoClient } = require("mongodb");
// Replace the uri string with your connection string.
const uri =
"mongodb://localhost:27017";
const client = new MongoClient(uri);
async function run() {
try {
const database = client.db('shopDB');
const products = database.collection('products');
// Query for a movie that has the title 'Back to the Future'
const query = { id: 1 };
const product = await products.findOne(query);
console.log(product);
//insert a new products
var newProduct = {id: 5, name: 'Cat', price: 10, stock: 0};
products.insertOne(newProduct, async function(err, res){
if (err) throw err;
console.log('1 product created');
})
} finally {
await client.close();
}
}
run().catch(console.dir);
If I execut the above code I get: Error “Cannot use a session that has ended” when inserting into a collection I assume it is because of the await client.close(); because if add this command to the callback func of my insertOne() func it works fine.
But if the command is in finally shouldn't the connection be terminated at the end of the function run()? Why is the session ended before I could insert my object?
Thank you for your help!
You are not awaiting for the insert and you code goes straight to finally to close the connection before the insert executes
await products.insertOne(newProduct)

TypeError: populate(...).exec is not a function

I largely believe this error is due to the object I'm calling not containing the .populate function, although I have no idea how to change this to work.
To start with, here is the error in full.
TypeError: exam[0].modules[u].topics[i].populate(...).exec is not a function
at /home/ubuntu/workspace/tests/app.js:425:84
at Query.Model.$wrapCallback (/home/ubuntu/workspace/tests/node_modules/mongoose/lib/model.js:3336:16)
at /home/ubuntu/workspace/tests/node_modules/mongoose/node_modules/kareem/index.js:259:21
at /home/ubuntu/workspace/tests/node_modules/mongoose/node_modules/kareem/index.js:127:16
at nextTickCallbackWith0Args (node.js:420:9)
at process._tickCallback (node.js:349:13)
Process exited with code: 1
The specific line I'm referring to is exam[0].modules[u].topics[i].populate("questions").exec(function(err,quests) another line I believe is significantly important here is the line examBoard.find({name:req.body.examBoardName},function(err,exam) which returns exam which does not contain the .populate function.
I presume this is largely down to my lack of experience, and not a logical error, but I'm not sure.
Here is the section of code which contains the error.
app.post("/test",function(req,res)
{
console.log("\n\n\n\n")
var time = req.body.time;
var topicName = [req.body.topic1,req.body.topic2,req.body.topic3,req.body.topic4,req.body.topic5];
var topicsArray = [];
examBoard.find({name:req.body.examBoardName},function(err,exam)
{
if(err)
{
console.log(err);
}
else
{
for(var u=0;u<exam[0].modules.length;u++)
{
console.log("exam[0].modules[u]:\n"+exam[0].modules[u]);
console.log("req.body.moduleName:\n"+req.body.moduleName);
if(exam[0].modules[u].name==req.body.moduleName)
{
console.log("topicName[]:\n"+topicName[0]+"\n"+topicName[1]+"\n"+topicName[2]+"\n"+topicName[3]+"\n"+topicName[4]);
for(var i=0;i<exam[0].modules[u].topics.length;i++)
{
console.log("exam[0].modules[u].topics[i].name:\n"+exam[0].modules[u].topics[i].name);
for(var t=0;t<topicName.length;t++)
{
if(exam[0].modules[u].topics[i].name==topicName[t])
{
// exam[0].modules[u].topics[i].find({name:topicName[t]}).populate("questions").exec(function(err,quests)
exam[0].modules[u].topics[i].populate("questions").exec(function(err,quests)
{
if(err)
{
console.log(err);
}
else
{
console.log("exam[0].modules[u].topics[i].questions:\n"+exam[0].modules[u].topics[i].questions);
topicsArray.push({
name:topicName[i],
questions:quests
});
}
});
}
}
}
break;
}
}
}
});
});
Here is the examBoard schema.
var mongoose = require("mongoose");
var topicSchema = new mongoose.Schema({
name: String,
questions:[
{
type:mongoose.Schema.Types.ObjectId,
ref:"question"
}
],
});
var moduleSchema = new mongoose.Schema({
name: String,
topics: [topicSchema]
});
var examBoardSchema = new mongoose.Schema({
name: String,
modules: [moduleSchema]
});
module.exports = mongoose.model("examBoard", examBoardSchema);
And here just in case there may be something wrong here, is the importing of the schema.
var express = require("express"),
mongoose = require("mongoose"),
passport = require("passport"),
bodyParser = require("body-parser"),
LocalStrategy = require("passport-local"),
passportLocalMongoose = require("passport-local-mongoose"),
seedDB = require("./seeds"),
question = require("./models/question"),
examBoard = require("./models/examBoard"),
user = require("./models/user");
You are invoking populate method from exam[0].modules[u].topics[i] but actually the model object that hold this method is exam[0] so you can populate questions in your exam in a deep object-hierarchy like this:
exam[0].populate("modules.topics.questions")
But, wait a sec, now the model will populate the questions in all topics in all modules within this exam.
In your case, you filter by moduleName first, so you can configure populate options, so to be like this:
var options = {
path: 'modules.topics.questions',
match: { 'modules.name': req.body.moduleName }
};
exam[0].populate(options)
Lear more about populate parameters from docs.

How to add binary data with objectId to mongoDB?

I need to insert a document into a collection, which has an ObjectId and a BinData value. Therefore I don't know how to insert it.
With this code I get the error TypeError: Cannot read property 'ObjectId' of undefined.
server/fixtures.js
var ObjectId = Mongo.ObjectID;
var chunk = {
"_id" : ObjectId("57a9be3c89c1e4b50c574e3a"),
"files_id": ObjectId("5113b0062be53b231f9dbc11"),
"n" : 0,
"data" : BinData(0, "/9j/4AAQSkZJRgA...and...so...on../2Q==")
};
db.mediafiles.chunks.insert(chunk);
Update
I'm using Meteor
Therefore I can use var ObjectId = Meteor.Collection.ObjectID;. But how do I get the BinData?
ReferenceError: BinData is not defined
Stumbled upon this today as well.
As the other answer mentioned you can use ObjectID and Binary provided by the MongoDB driver. The issue I had was that the binary data was not what I expected after inserting and this is due to the inner workings of the Binary function. It requires either an unencoded string or a buffer, which can be initialized from base64 encoded content like this:
const { Binary, ObjectID } = require('mongodb')
async function run() {
// Configure MongoDB connection
const client = new MongoClient()
// Connect to MongoDB
await client.connect(...)
try {
// Insert data using base64 encoded content and
// both ObjectID and Binary from mongodb package
await client.db().mediafiles.chunks.insert({
_id: ObjectID('57a9be3c89c1e4b50c574e3a'),
files_id: ObjectID('5113b0062be53b231f9dbc11'),
n: 0,
data: Binary(Buffer.from('/9j/4AAQSkZJRgA...and...so...on../2Q==', 'base64')),
})
} finally {
// Close client if it was opened
await client.close()
}
}
Here is the NodeJS code to insert data into collection. To answer your question specifically, you need the below statement if you are using NodeJS.
var ObjectId = require('mongodb').ObjectID;
Full NodeJS code (assuming you are using NodeJS):-
var Db = require('mongodb').Db, MongoClient = require('mongodb').MongoClient;
var assert = require('assert');
var ObjectId = require('mongodb').ObjectID;
var bindata = new require('mongodb').Binary("ZzEudm1s");
var insertDocument = function(db, callback) {
var chunk = {
"_id" : new ObjectId("535e1b88e421ad3a443742e7"),
"files_id" : new ObjectId("5113b0062be53b231f9dbc11"),
"n" : 0,
"data" : bindata
};
db.collection('Day1').insertOne(chunk, function(err, result) {
assert.equal(err, null);
console.log("Inserted a document into the collection.");
callback();
});
};
MongoClient.connect('mongodb://localhost:27017/test', function(err, db) {
assert.equal(null, err);
insertDocument(db, function() {
db.close();
});
});
If you need a pure JavaScript object of ObjectId, you can use the below library.
https://www.npmjs.com/package/objectid-purejs

How to mock pg in JavaScript?

I am new to mock concept and javascript programming either. I want to to mock pg (postgres module) in the javascript program. I can imitate very simple scenario, but in actual I don't.
Here is my userHandler.js:
var pg = require('pg');
var connectionString = process.env.DATABASE_URL || 'postgres://admin:admin#localhost:5432/mydb';
exports.handlePost = function(req,res){
var results = [];
// Grab data from http request
var adata = [req.body.Username, ..., req.body.FaxNum]; //Ignore for short.
// Get a Postgres client from the connection pool
pg.connect(connectionString, function(err, client, done) {
// SQL Query > Insert Data
var func_ = 'SELECT Dugong.Users_Add($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17,$18,$19)';
var addUser_ = client.query(func_, adata);
addUser_.on('error', function(error){
var data = {success : false,
username : req.body.Username,
reason : {errmsg : error.detail,
errid : 'addUser_' }};
return res.json(data);
});
addUser_.on('end',function(result){
var data = {success : true, username : req.body.Username};
console.log('Insert record completed');
return res.json(data);
});
// Handle Errors
if(err) {
console.log(err);
return ;
}
return;
});
};
And here is my unit test file. m_users_page.js:
var express = require('express');
var router = express.Router();
var test = require('unit.js');
var mock = require('mock');
var httpMocks = require('node-mocks-http');
var real_users_page = require('../routes/users_page.js');
var b = mock("../routes/userHandler.js", {
pg: {
connect: function (connectionString,callback) {
if(connectionString === 'postgres://admin:admin#localhost:5432/skorplusdb'){
console.log('333');
//pseudo object
var client = {query : function(func_, adata, cb){
cb(null,adata);
}};
client.on('error', 'test emit the error in my mock unit.');
//pseudo done object
var done = function(){};
callback(null, client, done);
return ;
}
}
}
}, require);
describe('Test with static login', function(){
it('Test simple login', function(done){
var request = httpMocks.createRequest({
method: 'POST',
url: '/users',
body: { Username:"Je", ..., FaxAreaCode:'232'} //Ignore for short
});
var response = httpMocks.createResponse();
b.handlePost(request,response, function(){
var data = response._getData();
console.log("7777777777" + data);
done();
});
});
});
Here is the error :
$ mocha testing/m_users_page.js
Test with static login
333
1) Test simple login
0 passing (7ms)
1 failing
1) Test with static login Test simple login:
TypeError: Object #<Object> has no method 'on'
at Object.mock.pg.connect (testing/m_users_page.js:22:14)
at Object.exports.handlePost (routes/userHandler.js:30:6)
at Context.<anonymous> (testing/m_users_page.js:63:5)
My questions are:
What is a proper way to do a unit test in Node + Express + Mock + node-mocks-http?
How to find good framework with well document I must read. After several days, I started to circling around the result from search engines. They are too simple, I can't adapt it to my problem.
First, make sure you understand the difference between unit tests and integration tests. If you want to test against the actual db, even if it has a dummy data set, that's an integration test and it doesn't need a mock: just connect to the database with the dummy data.
But suppose you want to test your webserver module, and you want to mock the db. First, pass the database module as a parameter rather than requiring pg directly. Also, wrap the postgres interface with your own class:
const { Pool } = require('pg');
module.exports = class DatabaseInterop {
// Connection parameters can be passed to the constructor or the connect method, parameters to
// DatabaseInterop::connect will override the initial constructor parameters.
constructor ({
user,
password,
database,
host,
logger={log: console.log, err: console.error},
}) {
this.logger = logger;
this._params = {
user,
password,
database,
host,
};
}
connect (params) {
const {
user,
password,
database,
host,
} = Object.assign({}, this._params, params);
this._pool = new Pool({
user,
password,
database,
host,
});
['SIGHUP', 'SIGINT', 'SIGQUIT', 'SIGILL', 'SIGTRAP', 'SIGABRT',
'SIGBUS', 'SIGFPE', 'SIGUSR1', 'SIGSEGV', 'SIGUSR2', 'SIGTERM'
].forEach(function (sig) {
process.on(sig, async () => {
logger.log(`Exiting for ${sig}...`);
process.exit(0);
});
});
return this;
}
async stop () {
return this._pool.end();
}
runQuery (queryString, params=[]) {
return params.length ? this._pool.query(queryString, params) : this._pool.query(queryString);
}
};
Now to mock it out, you can simply extend your custom class in your test file:
const DatabaseInterop = require('/path/to/database_interop.js');
class MockDB extends DatabaseInterop {
connect () {
// no-op
}
runQuery (qs, ...params) {
// return whatever
}
stop () {
// noop
}
}
Now for your tests you can inject the mock and your actual system inject the actual interface.

What is the correct way of passing a Mongoose object into the MongoDB underlying connection insert() method

I need to insert many thousands of documents into MongoDB. I want to use Mongoose for its casting properties, etc. However I cannot figure out how to pass the generated instances to the MongoDB connection. I have tried this:
var fs = require('fs');
var mongoose = require('mongoose');
var config = JSON.parse(fs.readFileSync("./config.json"));
mongoose.connect(config.mongoDBUrl);
db = mongoose.connection;
db.once('open', function () {
var TestSchema = new mongoose.Schema({
testStr : String
});
var Model = mongoose.model('test_schema_2', TestSchema);
var inst = new Model();
inst.testStr = "EWAFWEFAW";
// This works.
db.collection('test_schema_2').insert({ testStr : 'My Test Str'}, {}, function (err) {
if (err) {
console.log(err);
} else {
console.log('Written.');
db.close();
}
});
// This doesn't.
db.collection('test_schema_2').insert(inst, {}, function (err) {
if (err) {
console.log(err);
} else {
console.log('Written.');
db.close();
}
});
});
In the second case, I get: "[RangeError: Maximum call stack size exceeded]"
What is Mongoose breaking behind the scenes that stops this from working, and how can I make it work?
To save an instance of a model you just have to do
inst.save(function(err) {
//Do something here
});

Categories

Resources