MongoError: Topology is closed, please connect (client.close and client.connect issue)

Viewed 1952

(node:21140) UnhandledPromiseRejectionWarning: MongoError: Topology is closed, please connect

I think it is an error relating to repetition of client.connect and client.close in each function of CRUD operation as shown in code below

//I think there is no issue with cluster uri so I didn't post that since it connects fine

const MongoClient = require("mongodb").MongoClient;

const uri = "mongodb+srv://username:pasword@some cluster"; 
//I think there is no issue with cluster uri so I didn't post that since it connects fine


const client = new MongoClient(uri, { useNewUrlParser: true, useUnifiedTopology: true });



async function AddnewService(service) {
    await client.connect()
    const database = client.db("twitch")
   const usr = await database.collection("recipes").find({ Title: service.Title }).toArray()
    if (usr.length >= 1) {
        await client.close()
        return { message: "Title already in use" }
    }
    else {
        const result = await database.collection("recipes").insertOne(service)
        await client.close()
        return { message: "Service Added to Database" }
    }
}

async function FetchAllserviceSearch(service) {
    await client.connect()
    const database = client.db("twitch")
    let recipesCursor = await database.collection("recipes").find({})
    while (await recipesCursor.hasNext()) {                       //make sure to put await on cursor .hasnext()
        let recipe = await recipesCursor.next()
        console.log(recipe)
    }
    await client.close()

    return { message: "User Added to Database" }
}


module.exports = {
    AddnewService,
    FetchAllserviceSearch
}

I tried allot to make that client.connect and client.close be called only once but there have always be a new error caused by it. I cannot eliminate module.export cause I need them for usage in other file.

So I need a little help to make it work out some how without that needless repetition as mongodbAtlas causes issues with that.

2 Answers

Don't close your DB connection, you're in race condition, when two requests come one will close other will open.

client.close(); -----> remove

Use the same db connection for all the requests.

const uri = "mongodb+srv://username:pasword@cluster0.ik1pu.mongodb.net/myFirstDatabase?retryWrites=true&w=majority";

let con;

async function connect(service) {
   if (con) return con; // return connection if already conncted
   const client = new MongoClient(uri, { useNewUrlParser: true, useUnifiedTopology: true });
   con = client.connect()
   return con;
}


async function AddnewService(service) {
    const client = await connect();
    const database = client.db("twitch")
    const usr = await database.collection("recipes").find({ Title: service.Title }).toArray()
    if (usr.length >= 1) {
        // await client.close(); -----> remove
        return { message: "Title already in use" }
    }
    else {
        const result = await database.collection("recipes").insertOne(service)
        return { message: "Service Added to Database" }
    }
}

async function FetchAllserviceSearch(service) {
    const client = await connect();
    const database = client.db("twitch")
    let recipesCursor = await database.collection("recipes").find({})
    while (await recipesCursor.hasNext()) {                       //make sure to put await on cursor .hasnext()
        let recipe = await recipesCursor.next()
        console.log(recipe)
    }
    // await client.close(); -----> remove
    return { message: "User Added to Database" }
}


module.exports = {
    AddnewService,
    FetchAllserviceSearch
}

You are trying to close the client in a request handler, but your client is global.

If you want to have a global client, do not close it in request handlers.

If you want to close the client in request handlers, create the client in the handler of the same request.

Create a Connection class to manage the apps database connection. MongoClient does not provide a singleton connection pool so you don't want to call MongoClient.connect() repeatedly in your app. A singleton class to wrap the mongo client works for most apps I've seen.

    const MongoClient = require('mongodb').MongoClient

    class Connection {
    
        static async open() {
            if (this.db) return this.db
            this.db = await MongoClient.connect(this.url, this.options)
            return this.db
        }
    
    }
    
    Connection.db = null
    Connection.url = 'mongodb://127.0.0.1:27017/test_db'
    Connection.options = {
        bufferMaxEntries:   0,
        reconnectTries:     5000,
        useNewUrlParser:    true,
        useUnifiedTopology: true,
    }
    
    module.exports = { Connection }

Everywhere you require('./Connection'), the Connection.connectToMongo() method will be available, as will the Connection.db property if it has been initialised.

const { Connection } = require('../lib/Connection.js')

        async function AddnewService(service) {
        // This should go in the app/server setup, and waited for.
        await Connection.connectToMongo()
        const database = Connection.db("twitch")
       const usr = await database.collection("recipes").find({ Title: service.Title }).toArray()
        if (usr.length >= 1) {
           // await client.close()
            return { message: "Title already in use" }
        }
        else {
            const result = await database.collection("recipes").insertOne(service)
           // await client.close()
            return { message: "Service Added to Database" }
        }
    }

In express you can do something like this,

import {MongoClient} from 'mongodb';
import express from 'express';
import bodyParser from 'body-parser';
    let mongoClient = null;
    MongoClient.connect(config.mongoURL, {useNewUrlParser: true, useUnifiedTopology: true},function (err, client) {
        if(err) {
          console.log('Mongo connection error');
        } else {
          console.log('Connected to mongo DB');
          mongoClient = client;
        }
    })
let app = express();
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());

app.use((req,res,next)=>{
    req.db = mongoClient.db('customer_support');
    next();
});

And use in your request handles like this,

    router.post('/hello',async (req,res,next)=>{
    let uname = req.body.username;
    let userDetails = await AddnewService(req.db,uname) // change signature of this method
    res.statusCode = 200;
    res.data = userDetails;
    next();
});
Related