mongo client: how can I reuse the client in separate file? - javascript

Here is db.js file
const client = new MongoClient(DATABASE, mongodbOptions);
const connectWithMongoDb = () => {
client.connect((err) => {
if (err) {
throw err;
} else {
console.log('db connected');
}
});
};
module.exports = { client, connectWithMongoDb };
I called the connectWithMongoDb function from my server.js. db connects successfully. but the problem is I can't reuse the client. for example, I want to make a separate directory for collections. (in order to get a collection I need client object)
So, here is my collection.js file
const { client } = require('../helpers/db-helper/db');
exports.collection = client.db('organicdb').collection('products');
but the problem arises as soon as this file(collection.js) is called.
I am getting this error:
throw new MongoError('MongoClient must be connected before calling MongoClient.prototype.db'

You have to get the connection after connecting to MongoDB post that you can use it anywhere.
Read - https://mongodb.github.io/node-mongodb-native/api-generated/mongoclient.html
let client;
async function connect() {
if (!client) {
client = await MongoClient.connect(DATABASE, mongodbOptions)
.catch(err => { console.log(err); });
}
return client;
}
conet getConectedClient = () => client;
const testConnection = connect()
.then((connection) => console.log(connection)); // call the function like this
module.exports = { connect, getConectedClient };

Related

TypeError: Cannot read properties of undefined (reading 'find') When using Async/Await

I learning how to use javascript to work with the mongodb database, but I keep getting an error when initializing cursor. In the injectDB function, customers is defined, but once getCustomers() is called, customers ends up being undefined. How do I fix this?
var customers
export default class CustomersDAO{
static async injectDB(conn){
if(customers){
return
}
try {
customers = await conn.db(process.env.NS).collection("Customers")
//console.log(customers)
} catch(e){
console.error(`Unable to establish a collection handle in CustomersDAO, ${e}`)
}
console.log(`customers in function: ${customers}`)
}
static async getCustomers(){
let query = {first: "Yanely"}
let cursor
try {
cursor = await customers.find(query)
} catch(e){
console.error(`Unable to issue find command, ${e}`)
return {customersList: [], totalCustomers: 0}
}
console.log(cursor)
}
}
injectDB is being called in my index.js file
MongoClient.connect(
process.env.URI, {
maxPoolSize: 50,
wtimeoutMS: 2500,
useNewUrlParser: true
}
).catch(err => {
console.error(err.stack)
process.exit(1)
}).then(async client => {
await CustomersDAO.injectDB(client)
app.listen(port, () => {
console.log(`listening on port ${port}`)
})
})
while getCustomers is being called in my controller file
static async apiGetCustomers(req, res, next){
const {customersList, totalCustomers} = await CustomersDAO.getCustomers()
let response = {
customers: customersList,
total: totalCustomers
}
res.json(response)
}
}
I'm expecting for the customers variable to hold the connection made from the inject function, so I can use getCustomers() to retrieve the data from the db.

how can i store data in json file Continuous for discord js

I want to take the message information from the user and save it in a JSON file and this data is constantly added, but with the following code, this data is constantly replaced.
and I don't replace data I want to add data
this is my code :
const fs = require("fs");
const { Client, Intents } = require("discord.js");
const client = new Client({
intents: [Intents.FLAGS.GUILDS, Intents.FLAGS.GUILD_MESSAGES],
});
const now = new Date();
const obj = {
table: [],
};
let confirm = false;
const { badWords } = require("../badWordList.json");
client.on("message", async (message) => {
if (message.author.bot) return;
for (let i = 0; i < badWords.length; i++) {
if (message.content.toLowerCase().includes(badWords[i].toLowerCase()))
confirm = true;
}
if (confirm) {
obj.table.push({
name: message.author.username,
date: `${now.getFullYear()}/${now.getMonth()}/${now.getDate()}`,
message: message.channel.messages.channel.lastMessage.cleanContent,
});
fs.writeFile("myjsonfile.json", JSON.stringify(obj), function (err) {
if (err) throw err;
console.log("complete");
});
}
});
When using the fs.writeFile() it replaces the content of the file, as written in the docs.
At a first glance, you might want to use fs.write(), see the docs for usage.
But the NodeJS docs says :
It is unsafe to use fs.write() multiple times on the same file without waiting for the callback. For this scenario, fs.createWriteStream() is recommended.
Since you are in asynchronous mode, you shoud probably define a write stream to the file and then write to it, it gives you something like that :
// -snip-
const whateverJsonFileStream = fs.createWriteStream("myjsonfile.json");
client.on("message", async (message) => {
//-snip-
if (confirm) {
obj.table.push({
name: message.author.username,
date: `${now.getFullYear()}/${now.getMonth()}/${now.getDate()}`,
message: message.channel.messages.channel.lastMessage.cleanContent,
});
whateverJsonFileStream.write(JSON.stringify(obj), function (err) {
if (err) throw err;
console.log("complete");
});
}
});

Discord bot unable to get access to the Google Sheets getting error The request is missing a valid API key

I am having problem with my Discord bot trying to access the Google Sheets API v4.0. I want it to read, write, and update data in the sheet. My code work fine for Node.js application but when I put my code in a Discord bot, it gives me this error:
UnhandledPromiseRejectionWarning: Error: The request is missing a valid API key
I don't understand why I am getting this error. I even made a service account for my Discord bot.
Here is the Discord bot's code.
require('dotenv').config();
const sheet = require('./sheet');
const { Client } = require('discord.js');
const { sheets } = require('googleapis/build/src/apis/sheets');
const client = new Client();
Sheet file code:
const {google} = require('googleapis');
const keys = require('./keys.json');
const client = new google.auth.JWT(
keys.client_email,
keys.private_key_id,
keys.private_key,
['https://www.googleapis.com/auth/spreadsheets']
);
client.authorize(function(err,tokens){
if(err){
console.log(err);
return;
}
// else{
// console.log('Connected');
// gsrun(client);
// }
});
const getdata = async function gsrun(cl){
const gsapi = google.sheets({version:'v4',auth:cl});
const opt = {
spreadsheetId:'1LzdhD4rb2FdElTyQCAkgb5oyeGeu0d9rT2abS4n_4i8',
range: 'A2:B5'
};
let data = await gsapi.spreadsheets.values.get(opt);
console.log(data.data.values);
}
exports.getdata = getdata;
I have given access to bot email id to sheet which is discordbotapi#nodejs-api-testing-discordbot.iam.gserviceaccount.com. I have also enabled the Google Sheets API. Where am I making error?
When you tried it in Node.js without Discord.js, you didn't export anything and you simply called gsrun(client) after the authorisation was successful, so it worked fine. The problem is that now you try to use getData without any authorisation. Although you have client.authorize in your code, you never use it.
To solve this, I would make at least two different functions here; one for generating the client and one for the get request itself and export them both.
To generate a client I’d wrap this in a promise. This way I could use async/await later. This function will create a client with the JWT, perform the authorisation, and either resolve with the client or reject the promise depending on the results of client.authorize().
function connect() {
return new Promise((resolve, reject) => {
const scope = ['https://www.googleapis.com/auth/spreadsheets'];
const { client_email, private_key, private_key_id } = keys;
const client = new google.auth.JWT(
client_email,
private_key_id,
private_key,
scope,
);
client.authorize((err) => {
if (err) {
reject(err);
} else {
resolve(client);
}
});
});
}
Now you can simply connect and get the client by using const client = await connect().
The second part is to get some data from the spreadsheet. Again, I'd wrap it in a promise. This function will accept the client (we’ve just created above) and the options with the spreadsheetId and range. Inside the promise you just call the API endpoint with the options:
function getData(client, options) {
return new Promise((resolve, reject) => {
const endpoint = google.sheets({ version: 'v4', auth: client });
endpoint.spreadsheets.values.get(options, (err, data) => {
if (err) {
reject(err);
} else {
// or resolve with data.data.values if you only want the values
resolve(data.data);
}
});
});
}
You can export both of these in an object. Here is the full sheet.js file:
const { google } = require('googleapis');
const keys = require('./keys.json');
function connect() {
return new Promise((resolve, reject) => {
const scope = ['https://www.googleapis.com/auth/spreadsheets'];
const { client_email, private_key, private_key_id } = keys;
const client = new google.auth.JWT(
client_email,
private_key_id,
private_key,
scope,
);
client.authorize((err) => {
if (err) {
reject(err);
} else {
resolve(client);
}
});
});
}
function getData(client, options) {
return new Promise((resolve, reject) => {
const endpoint = google.sheets({ version: 'v4', auth: client });
endpoint.spreadsheets.values.get(options, (err, data) => {
if (err) {
reject(err);
} else {
resolve(data.data);
}
});
});
}
module.exports = { connect, getData };
Then, you can import it in your bot's file and use it in there to connect first and then get the values:
const { Client } = require('discord.js');
const { connect, getData } = require('./sheet');
const client = new Client();
client.on('message', async (message) => {
if (message.author.bot) return;
const auth = await connect();
const options = {
spreadsheetId: '1LzdhD4rb2FdElTyQCAkgb5oyeGeu0d9rT2abS4n_4i8',
range: 'A2:B5',
};
const { values } = await getData(auth, options);
message.channel.send(values);
});

AzureFN - NodeJS program is not inserting rows into CosmosDB

I have the following Azure Function in NodeJS
which has as trigger: IoT Hub events.
And I need to transfer the messages to cosmos DB.
module.exports = function (context, IoTHubMessage) {
try {
var dbName = "db";
var collectionName = "encodedmessages";
context.log(`JavaScript eventhub trigger function called for message array: ${IoTHubMessage}`);
var mongoClient = require("mongodb").MongoClient;
context.log('MongoClient created');
mongoClient.connect("mongodb://xxx:password==#xxx.mongo.cosmos.azure.com:10255/?ssl=true&replicaSet=globaldb&retrywrites=false&maxIdleTimeMS=120000&appName=#db#",{useNewUrlParser: true, authSource: dbName}, function (err, client) {
if(err){
context.log(`Error occurred while connecting to DB ${err}`)
} else{
context.log('MongoClient connected to DB');
}
var collection = mongoClient.db(dbName).collection(collectionName);
context.log('MongoClient collection retreived');
collection.insertOne(IoTHubMessage, {w: 1});
//collection.insertOne({"testKey": 13.56}, {w: 1});
mongoClient.close();
context.log(`Saved message: ${IoTHubMessage}`);
context.done();
});
} catch (e){
context.log(`Error ${e}`);
}
context.log('Done called');
context.done();
};
I also have a console app sending messages to the iot hub running as explained here:
https://learn.microsoft.com/en-us/azure/iot-hub/quickstart-send-telemetry-dotnet
The output is the following:
2020-10-30T12:06:41.968 [Information] JavaScript eventhub trigger function called for message array: Test Message
2020-10-30T12:06:41.972 [Information] MongoClient created
2020-10-30T12:06:41.972 [Information] Done called
2020-10-30T12:06:42.026 [Information] Executed 'Functions.ProcessEncodedMessages' (Succeeded, Id=2fcb7fa8-b194-4499-bc39-775aef86aac0, Duration=24606ms)
I dont really understand why I dont see in the logs messages in this piece of code:
if(err){
context.log(Error occurred while connecting to DB ${err})
} else{
context.log('MongoClient connected to DB');
}
Its like if its never reaching to that point, and I dont get any error regarding the connection string either.
I believe the insertOne function returns a promise and you're not awaiting it hence its moving to the next statement which was mongoClient.close() thereby closing the connection.
You can re-factor your code to use ES8 async-await and post resolving the insertOne function's promise schedule the call to close the connection.
Here's a reference from the official docs.
const { MongoClient } = require("mongodb");
module.exports = function (context, IoTHubMessage) {
const dbName = "db";
const collectionName = "encodedmessages";
const connectionString = `mongodb://xxx:password==#xxx.mongo.cosmos.azure.com:10255/?ssl=true&replicaSet=globaldb&retrywrites=false&maxIdleTimeMS=120000&appName=#db#`;
const options = {
useNewUrlParser: true,
authSource: dbName
};
context.log(`JavaScript eventhub trigger function called for message array: ${IoTHubMessage}`);
const client = new MongoClient(connectionString, options);
try {
context.log('MongoClient created');
await client.connect();
const database = client.db(dbName);
const collection = database.collection(collectionName);
context.log('MongoClient collection retreived');
const insertResult = await collection.insertOne(IoTHubMessage, {w: 1});
context.log(`Saved message: ${IoTHubMessage}`, insertResult);
context.done();
context.log('Done called');
} catch (e){
context.log(`Error ${e}`);
context.done();
} finally {
client.close();
}
};

MongoDB reusable custom javascript module

I would like to create a local Javascript module I can "require" in other files to handle all MongoDB CRUD operations.
I wrote something as:
-- dbConn.js file --
require('dotenv').config()
const MongoClient = require('mongodb').MongoClient
const ObjectID = require('mongodb').ObjectID
let _connection
const connectDB = async () => {
try {
const client = await MongoClient.connect(process.env.MONGO_DB_URI, {
useNewUrlParser: true,
useUnifiedTopology: true
})
console.log('Connected to MongoDB')
return client
} catch (err) {
console.log(error)
}
}
exports.findOne = async () => {
let client = await connectDB()
if (!client) {
return;
}
try {
const db = client.db("Test_DB");
const collection = db.collection('IoT_data_Coll');
const query = {}
let res = await collection.findOne(query);
return res;
} catch (err) {
console.log(err);
} finally {
client.close();
}
}
exports.findAll = async () => {
let client = await connectDB()
if (!client) {
return;
}
try {
const db = client.db("Test_DB");
const collection = db.collection('IoT_data_Coll');
const query = {}
let res = await collection.find(query).toArray();
return res;
} catch (err) {
console.log(err);
} finally {
client.close();
}
}
Then in another file (not necessary inside Express app), say
-- app.js ---
const findAll = require('./dbConn').findAll
const findOne = require('./dbConn').findOne
findAll().then(res => JSON.stringify(console.log(res)))
findOne().then(res => JSON.stringify(console.log(res)))
I wonder if it is correct?
I have to close the connection after each method/CRUD operation?
I was trying to use IIF instead of ".then", as:
(async () => {
console.log(await findOne())
})()
But I receive a weird error saying that findAll is not a function.
What's wrong with it?
Thanks.
It really depends on your use case which isn’t clear If you are using Express or just stand alone and how frequent are you planning to run app.js
Either way your code is expensive, each time you reference dbCon.js you are opening a new connection to the database.
So you can fix app.js by only requiring dbCon.js once and use it..
The best practice is to ofcourse use connection pooling https://www.compose.com/articles/connection-pooling-with-mongodb/

Categories

Resources