This error occurs when attempting database operations after the MongoDB client connection has been closed or when the topology (internal connection manager) was terminated. The most common cause is closing the client prematurely or attempting operations on a stale connection.
The MongoTopologyClosedError is thrown when you try to perform database operations on a MongoDB client whose topology has been closed. The "topology" refers to MongoDB's internal connection management system that handles connections to replica sets, sharded clusters, or standalone servers. This error typically indicates that client.close() was called (either explicitly or implicitly) while operations were still pending, or that you're attempting to reuse a closed client connection. Once a MongoDB client is closed, its topology is destroyed and cannot be reused—you must create a new client instance. In serverless environments (Lambda, Vercel), this error commonly occurs when connections are not properly managed using singleton patterns, or when the connection pool is exhausted due to improper connection lifecycle management.
The most critical fix is to await all operations before closing the client:
const { MongoClient } = require('mongodb');
const client = new MongoClient(uri);
async function run() {
try {
await client.connect();
const database = client.db('myDatabase');
const collection = database.collection('myCollection');
// ✅ Await ALL operations
const result = await collection.findOne({ name: 'test' });
await collection.insertOne({ name: 'example', value: 123 });
console.log('Operations completed:', result);
} finally {
// Only close after operations finish
await client.close();
}
}
run().catch(console.error);Do not close the client while operations are still running.
In Express, Next.js, or other long-running applications, keep one connection open:
// lib/mongodb.js
const { MongoClient } = require('mongodb');
const uri = process.env.MONGODB_URI;
const options = {};
let client;
let clientPromise;
if (!process.env.MONGODB_URI) {
throw new Error('Please add your MongoDB URI to .env.local');
}
if (process.env.NODE_ENV === 'development') {
// In development mode, use a global variable to preserve client across HMR
if (!global._mongoClientPromise) {
client = new MongoClient(uri, options);
global._mongoClientPromise = client.connect();
}
clientPromise = global._mongoClientPromise;
} else {
// In production mode, create a new client
client = new MongoClient(uri, options);
clientPromise = client.connect();
}
module.exports = clientPromise;// Usage in API routes or pages
const clientPromise = require('./lib/mongodb');
async function getUsers() {
const client = await clientPromise;
const db = client.db('myDatabase');
return db.collection('users').find().toArray();
}This keeps the connection alive and prevents topology closure.
For AWS Lambda, Vercel, or other serverless platforms, use module-level caching:
const { MongoClient } = require('mongodb');
const uri = process.env.MONGODB_URI;
let cachedClient = null;
async function connectToDatabase() {
if (cachedClient) {
return cachedClient;
}
const client = new MongoClient(uri, {
maxPoolSize: 10,
minPoolSize: 1,
maxIdleTimeMS: 120000, // 2 minutes
});
await client.connect();
cachedClient = client;
return client;
}
// Lambda handler
exports.handler = async (event) => {
const client = await connectToDatabase();
const db = client.db('myDatabase');
const result = await db.collection('data').findOne({ id: event.id });
// ⚠️ Do NOT close in serverless - reuse connection
// await client.close(); // ❌ WRONG
return {
statusCode: 200,
body: JSON.stringify(result),
};
};The connection is reused across invocations within the same function instance.
Create a new client instance if you need to reconnect after closing:
const { MongoClient } = require('mongodb');
let client = null;
async function getClient() {
// Check if client exists and is connected
if (client && client.topology && client.topology.isConnected()) {
return client;
}
// Create new client if needed
client = new MongoClient(uri);
await client.connect();
return client;
}
// Usage
async function performOperation() {
const currentClient = await getClient();
const db = currentClient.db('myDatabase');
return db.collection('data').find().toArray();
}
// Graceful shutdown
async function shutdown() {
if (client) {
await client.close();
client = null; // ✅ Set to null so new client is created next time
}
}
process.on('SIGTERM', shutdown);
process.on('SIGINT', shutdown);Adjust connection settings to prevent premature closures:
const { MongoClient } = require('mongodb');
const client = new MongoClient(uri, {
maxPoolSize: 50,
minPoolSize: 5,
maxIdleTimeMS: 300000, // 5 minutes
serverSelectionTimeoutMS: 5000,
socketTimeoutMS: 45000,
connectTimeoutMS: 10000,
});
await client.connect();
// Monitor topology events
client.on('topologyClosed', () => {
console.error('MongoDB topology closed unexpectedly');
});
client.on('close', () => {
console.log('MongoDB connection closed');
});
client.on('error', (error) => {
console.error('MongoDB client error:', error);
});These settings help maintain stable connections and provide visibility into topology state.
Cannot Reuse After Close: After calling client.close(), the MongoClient instance is permanently destroyed and cannot be reconnected. The official MongoDB documentation states this is by design—you must create a new client instance if you need to reconnect.
Serverless Connection Leaks: Some serverless platforms can leak connections when functions are suspended. The real issue isn't the number of connections during normal operation, but that connections may not be properly cleaned up when execution contexts freeze. The singleton pattern mitigates this by reusing connections across warm starts.
maxIdleTimeMS Tuning: For serverless environments, set maxIdleTimeMS to match your platform's timeout limit (15 minutes for Lambda, 10 seconds for Vercel). This prevents connections from staying open longer than the execution context.
Mongoose Alternative: If using Mongoose, it handles connection state internally with built-in reconnection logic. Use mongoose.connection.readyState to check connection status (0=disconnected, 1=connected, 2=connecting, 3=disconnecting).
Calling close() vs Not Calling It: In Node.js driver documentation, calling close() does not terminate all connections immediately if operations are still running—it waits for pending operations to complete. However, there is only one JavaScript thread executing, so for long-running applications, it's better to keep connections open for the application's lifetime rather than repeatedly opening and closing them.
Meteor 3 Migration: Users migrating to Meteor 3 reported MongoTopologyClosedError at startup due to framework changes in connection handling. Solutions involved updating connection configuration and ensuring proper initialization sequencing.
DivergentArrayError: For your own good, using document.save() to update an array which was selected using an $elemMatch projection will not work
How to fix "DivergentArrayError: For your own good, using document.save() to update an array which was selected using an $elemMatch projection will not work" in MongoDB
MongoServerError: bad auth : authentication failed
How to fix "MongoServerError: bad auth : authentication failed" in MongoDB
CannotCreateIndex: Cannot create index
CannotCreateIndex: Cannot create index
StaleShardVersion: shard version mismatch
How to fix "StaleShardVersion: shard version mismatch" in MongoDB
MongoOperationTimeoutError: Operation timed out
How to fix "MongoOperationTimeoutError: Operation timed out" in MongoDB