// ✅ Create ONE MongoClient per process — never per request // The client manages a connection pool internally let client // module-level singleton export async function getDb() { if (!client) { client = new MongoClient(process.env.MONGODB_URI, { maxPoolSize: 50, // adjust per service (default 100) minPoolSize: 5, // keep warm connections serverSelectionTimeoutMS: 5000, // fail fast if no server found retryWrites: true, // auto-retry on network error (default) retryReads: true, w: "majority", // default write concern compressors: ["snappy", "zlib"] // wire compression }) await client.connect() process.on("SIGTERM", () => client.close()) process.on("SIGINT", () => client.close()) } return client.db("myDatabase") } // ❌ ANTI-PATTERN: creating a new client per request app.get("/orders", async (req, res) => { const client = new MongoClient(uri) // creates new pool per request! await client.connect() const orders = await client.db("mydb").collection("orders").find().toArray() await client.close() // closes pool before response res.json(orders) }) // ✅ Connection string best practices: // Always specify authSource for non-admin users // mongodb://appUser:pass@host:27017/mydb?authSource=mydb&replicaSet=myRS // Use SRV for Atlas: mongodb+srv://... // Never hardcode credentials — use env vars or secrets manager
Creating a new
MongoClient per request is the most common and most damaging MongoDB anti-pattern in applications. Each client creates its own connection pool. Under load, this exhausts the server's connection limit (~200 by default in standalone, 128 per mongos in Atlas). Always share a single client instance across the entire application lifecycle.