How to connect to MongoDB with Async/Await in Nodejs? - javascript

I have a Connection class with static methods that allows me to create a singleton type object for MongoDB connections. Using Async along with Await, I never get the connection to 'fire' before the rest of my code executes.
Using the traditional Promise / .then this Connection class works. Using Latest Nodejs version and MongoDB version.
static connectDb() {
//If MongoDB is already connected, return db object
if (this.dbClient) {
//const currDbClient = Promise.resolve(this.dbClient);
console.log(`MongoDB already connected!`);
return this.dbClient;
}
//Otherwise connect
else {
async () => {
try {
const newDbClient = await MongoClient.connect(this.url, this.options);
console.log(`DB is connected? ${newDbClient.isConnected()}`);
ConnectMeCore.dbClient = newDbClient;
return newDbClient;
} catch (error) {
console.error(`MongoDB connection failed with > ${error}`);
}
};
}
}
I expect the await to 'wait' for the DB to connect, or at least resolve the promise.

Thanks to #JaromandaX for helping find the answer!
The calling code can use a Promise.then to execute code once the DB connection happens.
DbConnection.connectDb().then(() => {
console.log("Is it connected? " + DbConnection.isConnected());
//Do CRUD
DbConnection.closeDb();
});
You can import this method(as part of 'Connection' class) into any class that needs to have a DB connect. A singleton for on DB connection. The working method fragment is as follows.
static async connectDb() {
//If MongoDB is already connected, return db object
if (this.dbClient) {
const currDbClient = Promise.resolve(this.dbClient);
console.log(`MongoDB already connected!`);
return currDbClient;
}
//Otherwise connect using 'await', the whole methos is async
else {
try {
const newDbClient = await MongoClient.connect(this.url, this.options);
console.log(`DB is connected? ${newDbClient.isConnected()}`);
this.dbClient = newDbClient;
return newDbClient;
} catch (error) {
console.error(`MongoDB connection failed with > ${error}`);
throw error;
}
}
}

Related

How to close a MongoDB connection in nodejs

I have the following method to connect to MongoDB:
import { Db, MongoClient } from 'mongodb';
let cachedConnection: { client: MongoClient; db: Db } | null = null;
export async function connectToDatabase(mongoUri?: string, database?: string) {
if (!mongoUri) {
throw new Error(
'Please define the MONGO_URI environment variable inside .env.local'
);
}
if (!database) {
throw new Error(
'Please define the DATABASE environment variable inside .env.local'
);
}
if (cachedConnection) return cachedConnection;
cachedConnection = await MongoClient.connect(mongoUri, {
useNewUrlParser: true,
useUnifiedTopology: true,
}).then((client) => ({
client,
db: client.db(database),
}));
return cachedConnection!;
}
And I use it in the following way:
const { db, client } = await connectToDatabase(
config.URI,
config.USERS_DATABASE
);
const user = await db
.collection(config.USERS_COLLECTION)
.findOne({ _id: new ObjectId(userId) });
It seems to be ok, but it is not. The problem of this method is that it doesn't close the connections. For example I have a cluster on Atlas, and the connections keep growing till 500. after that it doesn't serve anymore, goes in timeout and then my backend crashes.
To solve this I tried with client.close() just before returning the response to frontend.
It throws me one error saying MongoError: Topology is closed, please connect. I believe that it closes the connection before it finishes? is it right? Even if I put it after the DB responded.
this is the screenshot of the error:
Do you think there is a way to solve this or I just have to do the entire procedure in each file I need to connect to mongo? Do you also think I did something wrong?

"mongoError: Topology was destroyed" when trying to delete/update a document

I am trying to make a discord bot from NodeJS that utilizes MongoDB for its database. When I try to delete or update a document, sometimes, it returns mongoError: Topology was destroyed. I have read up on this error before and it says that the connection was being interrupted.
Here is the code for my Database Handler:
class DatabaseHandler {
constructor(client) {
this.client = client;
}
async connect(callback) {
try {
await this.client.connect();
await callback(this.client);
} catch (err) {
console.error(err);
} finally {
this.client.close();
console.log("CLIENT CLOSED");
}
}
}
module.exports = DatabaseHandler;
Here is the place that the error occurs:
DB.connect(async (client) => {
console.log(ObjectId(this._id));
let DBList = await client.db("Giveaways").collection("giveawayData");
let delVal = {
_id: ObjectId(this._id)
};
await DBList.deleteOne(delVal); // error occurs here
})
I do not think it is because of the this.client.close() because it is executed after all of the operations are finished.

Can't use #Res() with FilesInterceptor()

I am trying to upload a file using builtin multer and after then sending the response back to the user for success or failure. It was all going good until today, when I try to upload the Response wont come. after digging a bit I find out that when i use #res with #UploadedFile it does not execute the controller. I am new to nest.js.
Working.
#Post('uploads/avatar')
async uploadFile(#Req() req, #UploadedFile() avatar) {
console.log(req.body);
if (!req.body.user_id) {
throw new Error('id params not found.');
}
try {
const resultUpload = await this.userService.uploadUserImage(
req.body.user_id,
avatar,
); // returns the url for the uploaded image
return resultUpload;
} catch (error) {
console.log(error);
return error;
}
}
Not Working.
#Post('uploads/avatar')
async uploadFile(#Req() req, #UploadedFile() avatar, #Res() res) {
console.log(req.body);
if (!req.body.user_id) {
throw new Error('id params not found.');
}
try {
const resultUpload = await this.userService.uploadUserImage(
req.body.user_id,
avatar,
); // returns the url for the uploaded image
return resultUpload;
res.send(resultUpload);
} catch (error) {
console.log(error);
res.send(error);
}
}
In nest, you should always avoid injecting #Res because then you lose a lot of things that make nest so great: interceptors, exception filters,...
And actually, in most cases you don't need #Res since nest will automatically handle sending the response correctly.
If you want to send data from a controller method, you can just return the data (Promises and Observables will be resolved automatically as well). If you want to send an error to the client, you can just throw the corresponding HttpException, e.g. 404 -> NotFoundException:
#Post('uploads/avatar')
async uploadFile(#Req() req, #UploadedFile() avatar) {
if (!req.body.user_id) {
// throw a 400
throw new BadRequestException('id params not found.');
}
try {
const resultUpload = await this.userService.uploadUserImage(
req.body.user_id,
avatar,
);
return resultUpload;
} catch (error) {
if (error.code === 'image_already_exists') {
// throw a 409
throw new ConflictException('image has already been uploaded');
} else {
// throw a 500
throw new InternalServerException();
}
}
}
If for some reason you have to inject #Res here, you cannot use the FilesInterceptor. Then you have to configure the multer middleware yourself.
Side note
You can create a custom decorator for accessing the userId:
import { createParamDecorator } from '#nestjs/common';
export const UserId = createParamDecorator((data, req) => {
if (!req.body || !req.body.user_id) {
throw new BadRequestException('No user id given.')
}
return req.body.user_id;
});
and then use it in your controller method like this:
#Post('uploads/avatar')
async uploadFile(#UserId() userId, #UploadedFile() avatar) {
look, when you are using an interceptor, you are handling (with using .handle()) the stream of response (observable) not a whole package of it, but using express #Res actually is somehow getting around the whole flow of response streaming.
this is also explicitly mentioned in nestjs official documents:
We already know that handle() returns an Observable. The stream
contains the value returned from the route handler, and thus we can
easily mutate it using RxJS's map() operator.
WARNING
The response mapping feature doesn't work with the
library-specific response strategy (using the #Res() object directly
is forbidden).

Connecting to mongo database through constructor

I have a mongo database that I would like to connect to when I create an instance of my mongo manager class. From the constructor I am calling the connect routine and trying to set the db which should be returned back from the mongo source. I am however not getting anything returned when I call MongoDb.MongoClient.connect and the callback function isn't trigger at all
class MongoManager {
constructor(url) {
this.url = url;
this.db = {};
this.connect(this.url);
}
connect(url) {
MongoDb.MongoClient.connect(this.url, {
connectTimeoutMS: 15000
}, (err, db) => {
console.log("hello");
if (err) {
throw err;
}
this.db = db;
});
this.db = db;
}
}
}
Since I'm not getting the callback from MongoDb.MongoClient.connect() I am not able to set the db at the constructor level
P:S:- url for mongo client above is coming fine when i console log it i.e., mongodb://localhost:27017/dts
From the above statement the console.log("hello") never gets triggerred

UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 22): ReferenceError: client is not defined

This error seems to be coming up on every http request I make. I'm not totally sure where it's coming from?
(node:39390) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 22): ReferenceError: client is not defined
There is no line number in the error, but here is a sample of code that seems to be causing it:
try {
var client = await pool.connect();
await client.query(queryStatement, queryArgumentsArray);
res.sendStatus(200);
} catch (e) {
console.log('Error adding updating subvendor availability data, UPDATE SQL query task', err);
res.sendStatus(500);
} finally {
client && client.release && client.release();
}
It first I thought it must be coming from my finally block (maybe client was out of scope), but I added and if statement to explicitly prevent attempting to call client.release if it doesn't exist:
if (client) { client && client.release && client.release() };
I am still getting this error, so I feel like it must be coming from these lines.
var client = await pool.connect();
await client.query(queryStatement, queryArgumentsArray);
res.sendStatus(200);
Am I misunderstanding how to use async? To be clear, the code is functioning well and the http requests are working (responding correctly to the requests), my terminal is just getting flooded with these warnings.
Here is a simplified version of the full route:
var express = require('express');
var router = express.Router();
var pool = require('../modules/pg-pool'); // brings in pg-pool created in another module
// This route updates the availability for a user
router.put('/updateAvailability', async (req, res) => {
var userId = req.decodedToken.userSQLId;
var subvendorId = req.headers.subvendor_id;
var availability = req.body;
var queryStatement = 'UPDATE status SET status=$3 WHERE userId=$2';
var queryArgumentsArray = [availability.status, userId ];
try {
var client = await pool.connect();
await client.query(queryStatement, queryArgumentsArray);
res.sendStatus(200);
} catch (e) {
console.log('Error updating subvendor availability data, UPDATE SQL query task', err);
res.sendStatus(500);
} finally {
client && client.release && client.release();
}
});
module.exports = router;
All credit here goes to brianc the creator of node-postgres who answered my question here after I received the suggestion that it might be a problem with that library (doesn't seem like it was). I simply needed create the client outside of the try-catch
var client = await pool.connect()
try {
await client.query(...)
res.sendStatus(200)
} catch { ...}
finally {
}
His full answer can be found here: https://github.com/brianc/node-postgres/issues/1301

Categories

Resources