node.js 22 下必须用 mongodb@7.5+ 并全局单例初始化 mongoclient,禁止在路由中每次 new + connect,否则连接数爆炸、触发 econnrefused 或 maxlistenersexceededwarning;因每个 mongoclient 默认维护 100 连接池,重复实例化导致 socket 堆积、nat 超时及 tls v1.3 兼容问题。

直接说结论:Node.js 22 下必须用 mongodb@7.5+,且连接不能每次请求都 new MongoClient() 再 .connect(),否则会快速耗尽连接池、触发 MaxListenersExceededWarning 或 ECONNREFUSED。
为什么 new MongoClient() 不能放在路由处理函数里
这是最常踩的坑。很多初学者写 Express 路由时,习惯在每个 GET /user 里 new 一个 client,然后 connect、操作、close。问题在于:
-
MongoClient实例自带连接池(默认 100 个 socket),每次.connect()都会尝试建立新连接,而不是复用 -
.close()不会立即释放底层 socket,而是标记为可回收;高并发下大量 pending 连接堆积,触发操作系统级连接数限制 - Node.js 22 的
net模块对短生命周期 socket 更敏感,容易报connect EADDRINUSE或read ECONNRESET
mongodb@7.5 的连接写法必须用 async/await + 单例模式
新版驱动已废弃回调式 connect(),也不再支持传 serverOptions 和 dbOptions 这类旧参数。正确初始化方式是:
import { MongoClient } from 'mongodb';
const client = new MongoClient('mongodb://localhost:27017', {
// ⚠️ 注意:w: -1 已被移除,writeConcern 必须显式设为 { w: 1 } 或更高
writeConcern: { w: 1 },
// maxPoolSize 默认是 100,线上建议设为 20~50,避免撑爆服务器内存
maxPoolSize: 30,
// 保持连接活跃,防止 NAT 超时断连(尤其在云环境如 Azure DocumentDB)
minPoolSize: 5,
// 连接超时设短一点,避免卡住整个请求链路
connectTimeoutMS: 3000,
socketTimeoutMS: 5000
});
let db;
export async function getDb() {
if (!db) {
await client.connect(); // 只执行一次
db = client.db('myapp');
}
return db;
}
后续所有 DAO 层调用都基于 getDb() 返回的 Db 实例,不再重复 connect。
Azure DocumentDB 或 MongoDB Atlas 的连接字符串要加 ?tls=true
Node.js 22 默认启用更强的 TLS 策略(TLS v1.3 强制),而老版本驱动或没配 tls 的连接串会直接失败,错误信息通常是:
详细的 Three.js 3D 图形参考,涵盖场景设置、相机、几何体、材质、光照、动画、控制器、加载器、数学工具和调试。
MongoServerSelectionError: connect ECONNREFUSED ::1:27017(本地)或 getaddrinfo ENOTFOUND xxx.mongo.cosmos.azure.com(Azure)
解决方法:
- Azure DocumentDB:连接串末尾必须加
?tls=true&retryWrites=true&maxIdleTimeMS=60000&minPoolSize=5 - MongoDB Atlas:确保连接串含
&tls=true,且 Node.js 22 环境变量中未设置NODE_TLS_REJECT_UNAUTHORIZED=0(该设置已被视为不安全,新版驱动会忽略) - 本地开发若用自签名证书,需额外加
&tlsAllowInvalidCertificates=true,仅限测试环境
command() 执行管理命令时要注意选项限制
从 mongodb@7.0 开始,db.command() 方法只接受极少数顶层选项(如 comment、readPreference),其余必须嵌套在命令文档内。常见错误写法:
// ❌ 错误:把 writeConcern 放在 options 里
await db.command({ ping: 1 }, { writeConcern: { w: 'majority' } });
// ✅ 正确:writeConcern 是命令的一部分
await db.command({ ping: 1, writeConcern: { w: 'majority' } });
另外,runCursorCommand() 仅适用于返回游标结果的命令(如 listCollections),普通命令一律用 command()。
真正麻烦的不是怎么连上,而是连上之后怎么不让连接在半夜悄悄泄漏——检查 client.topology?.s?.connections?.length 是最直接的验证方式,值长期 > 10 就说明有地方没 properly close cursor 或没复用 client。










