Menu

Node.js 操作 MongoDB:驱动示例(查询、插入、更新)

用官方 mongodb 驱动在 Node.js 中使用 MongoDB:用 MongoClient 连接,然后在集合中插入、查询、更新和删除文档,全部使用 async/await。

本页包含可运行的编辑器。编辑、运行,立即查看输出。

要在 Node.js 中使用 MongoDB,先安装官方驱动(npm install mongodb),用 new MongoClient(uri) 连接,用 client.db('name') 选择数据库,用 db.collection('name') 选择集合。每个操作(insertOne、find、updateOne、deleteOne)都返回一个 Promise,所以你要 await 它。文档存进去和取出来都是普通的 JavaScript 对象。

它打印 [ 'Dune', 'Neuromancer', 'Hyperion' ]。shop 数据库和 books 集合之前并不存在:MongoDB 在第一次插入时创建了它们,而 toArray() 加载了 find() 游标指向的所有文档。

用筛选条件和投影查询文档

筛选条件是一个描述你想要哪些文档的对象。普通值表示精确匹配;$gt、$lt、$in、$regex 等运算符写在一个嵌套对象里。第二个参数中的 projection 用来选择返回哪些字段。

recent 中是 1960 年之后最新的两本书,先是 Hyperion,然后是 Neuromancer,只带 title 和 year 字段;findOne 返回第一个匹配的文档(或 null),countDocuments 统计出价格在 10 及以上的书有 2 本。把 $gt: 1960 改成 $gt: 1985,就只剩下 Hyperion 了。

更新和删除

updateOne(filter, update) 修改第一个匹配的文档。更新要用运算符:$set 替换字段,$inc 给数字加上一个值,$unset 删除字段,$push 向数组追加元素。updateMany 和 deleteMany 作用于所有匹配的文档。

这次更新匹配并修改了一个文档,所以 Dune 现在价格是 10,库存是 2;删除没有库存的那本书后,剩下 1 本。更新必须使用运算符:不带 $set 直接传入 { price: 10 },驱动会拒绝它(替换整个文档要用 replaceOne)。

ObjectId 和索引

每个文档都有一个 _id;如果你没有设置,驱动会创建一个 ObjectId。要按一个以字符串形式传来的 id(比如来自 URL)查找文档,就把它包进 new ObjectId(id)。索引能让某个字段的查询变快,唯一索引则会让 MongoDB 拒绝重复值。

ObjectId 转成字符串是 24 个十六进制字符,直接用这个字符串查询什么也找不到,所以那一行打印 null。第二次插入违反了唯一索引,以错误码 11000 失败,你就是这样检测“邮箱已被占用”的。

在 Express API 中使用 MongoDB

在服务器启动时连接一次,然后共享这个集合;不要为每个请求都打开一个新连接。驱动会维护一个连接池并重复使用其中的连接。

GET /books 返回两本书,每本都带一个 _id。把其中一个 id 复制到 GET /books/<id> 中,就能获取单本书;或者发送 POST /books,请求体是 {"title": "Foundation", "year": 1951} 这样的 JSON,然后再列出所有书看看。

真实的应用会从 process.env.MONGODB_URI 读取连接字符串,而不是把它写在代码里。

如果你想在驱动之上使用 schema 和验证,可以用封装了驱动的 Mongoose(npm install mongoose):

const Book = mongoose.model('Book', new mongoose.Schema({ title: String, year: Number }));
await Book.create({ title: 'Dune', year: 1965 });

常见错误

忘记调用 toArray()。 find() 返回的是一个游标,而不是文档。打印它只会看到一个游标对象;要调用 toArray(),或者用 for await 遍历它。

每个请求都建立连接。 每个 new MongoClient 都会打开自己的连接池。在启动时创建一个客户端,然后到处复用它。

脚本里从不关闭客户端。 代码执行完后,一个打开着的客户端会让进程一直运行。在脚本末尾调用 await client.close();服务器则在整个生命周期内保持它打开。

常见问题

应该用 mongodb 驱动还是 Mongoose?

驱动是官方提供的更薄的一层:你直接操作普通对象和 MongoDB 自己的查询语言。Mongoose 在它之上加了 schema、验证和模型,适合希望每个文档都经过检查的场景。两者连接的是同一个数据库,Mongoose 底层用的就是这个驱动。

需要先创建数据库或集合吗?

不需要。第一次向它们插入文档时,MongoDB 会自动创建两者。client.db('shop') 和 db.collection('books') 只是返回句柄。

连接字符串应该放在哪里?

放在 MONGODB_URI 这样的环境变量中,用 process.env.MONGODB_URI 读取。托管数据库的连接字符串里包含密码,所以绝不能提交到代码仓库。

为什么按 id 查询时 find 找不到文档?

_id 存储的是 ObjectId,而不是字符串,所以 { _id: '65f1...' } 什么也匹配不到。先转换一下:{ _id: new ObjectId(id) }。

Coddy 编程语言插图

和 Coddy 一起学编程

开始