在 Node.js 中操作 MongoDB
🎯 引言
前面几篇我们都是在 mongosh 里手写命令操作数据库。实际项目里,这些操作是由后端代码执行的:用户发起请求,Node.js 服务连上 MongoDB,查出数据再返回给浏览器。
学完这篇文章,你能用官方 mongodb 驱动在 Node.js 中连接 MongoDB,写出完整的增删改查代码,并结合 Express 提供一个返回用户列表的接口。这是把数据库知识和后端开发真正打通的关键一步。
🧱 安装与准备
mongodb 是 MongoDB 官方推出的 Node.js 驱动,提供了连接数据库、操作集合的全部能力,由官方团队维护,是 Node.js 生态里访问 MongoDB 的常用选择。
本文及本课程基于 Node.js v22 LTS、mongodb 7.x(官方驱动)编写和验证。新建一个项目目录,初始化后安装:
mkdir node-mongo && cd node-mongo
npm init -y
npm install mongodb
在开始写代码前,先确认 MongoDB 容器还在运行:
docker ps
如果列表里没有 mongo-demo,用 docker start mongo-demo 把它启动起来。
blog_demo 数据库的 user 集合展开,字段保持 username、age、city 不变。如果集合里没有数据,可以先在 mongosh 里插入几条,也可以直接用后面的代码新增。✨ 用 MongoClient 连接数据库
连接 MongoDB 需要一个连接串,格式像网址一样,依次写清楚协议、账号密码、地址和端口:
mongodb://root:123456@localhost:27017
代码里用 MongoClient 建立连接,再依次拿到数据库和集合:
const { MongoClient } = require('mongodb');
const url = 'mongodb://root:123456@localhost:27017';
const client = new MongoClient(url);
async function main() {
try {
await client.connect();
console.log('连接成功');
const db = client.db('blog_demo');
const user = db.collection('user');
// 后续操作都基于 user 这个集合
} finally {
await client.close();
}
}
main();
几个关键步骤:
new MongoClient(url):创建客户端实例,此时还没有真正连接。await client.connect():发起连接,网络不通或账号密码错误会在这里抛错。client.db('blog_demo'):选中数据库;db.collection('user'):选中集合。这两步只是拿到操作入口,不会真的访问数据库。try/finally保证无论中间是否出错,最后都会执行client.close()关闭连接,避免脚本卡住不退出。
可以打个比喻:client 像拨通电话,db 像找到对应的部门,collection 像找到部门里具体负责的那个人,找到人之后才能开始办事。
.env 文件配合 process.env 读取),避免提交到代码仓库泄露。学习阶段写死即可,心里有这根弦就行。🛠 完整的增删改查
下面这个 crud.js 覆盖了日常开发中的完整 CRUD:新增、查列表、按 id 查单个、更新、删除。
const { MongoClient, ObjectId } = require('mongodb');
const url = 'mongodb://root:123456@localhost:27017';
const client = new MongoClient(url);
async function main() {
try {
await client.connect();
const user = client.db('blog_demo').collection('user');
// 新增用户
const result = await user.insertOne({
username: '小美',
age: 22,
city: '深圳',
});
console.log('新增成功,_id =', result.insertedId);
// 查询用户列表
const list = await user.find().toArray();
console.log('用户列表:', list);
// 按 id 查询单个用户
const one = await user.findOne({ _id: new ObjectId(result.insertedId) });
console.log('按 id 查询:', one);
// 更新用户
await user.updateOne(
{ _id: new ObjectId(result.insertedId) },
{ $set: { age: 23 } }
);
// 删除用户
await user.deleteOne({ _id: new ObjectId(result.insertedId) });
} finally {
await client.close();
}
}
main();
运行 node crud.js,就能看到新增、查询、更新、删除依次生效。
ObjectId 的用法:MongoDB 的 _id 不是普通字符串,而是一个特殊的对象类型。从 mongodb 包里引入 ObjectId,用 new ObjectId(id字符串) 包一层才能按 id 查询,直接传字符串是查不到的。这是新手很容易踩的坑。💡 结合 Express 写一个接口
最后把知识串起来,写一个简洁的 Express 接口:访问 GET /users 返回用户列表。Express 基于 5.x 版本,先安装:
npm install express
const express = require('express');
const { MongoClient } = require('mongodb');
const url = 'mongodb://root:123456@localhost:27017';
const client = new MongoClient(url);
const app = express();
app.get('/users', async (req, res) => {
const user = client.db('blog_demo').collection('user');
const list = await user.find().toArray();
res.json(list);
});
async function main() {
await client.connect();
app.listen(3000, () => {
console.log('服务已启动:http://localhost:3000');
});
}
main();
和一次性脚本不同,服务是长期运行的,所以我们只在启动时连接一次数据库,之后每个请求都复用这个连接,不需要每次请求都 connect 和 close。
运行 node server.js,浏览器访问 http://localhost:3000/users,就能看到 JSON 格式的用户列表。到这里,浏览器、Node.js、MongoDB 这条完整链路就打通了。
🧾 小节总结
- mongodb 是官方 Node.js 驱动,连接串格式为
mongodb://账号:密码@地址:端口。 - 用
new MongoClient(url)创建客户端,await client.connect()建立连接,try/finally保证client.close()一定执行。 - 通过
client.db('blog_demo')选数据库、db.collection('user')选集合,拿到集合后即可操作。 insertOne新增后用result.insertedId拿新文档的_id;find().toArray()查列表;findOne查单个;updateOne配合$set更新;deleteOne删除。_id是 ObjectId 类型,按 id 查询要用new ObjectId(id)转换,不能直接传字符串。- Express 服务在启动时连接一次数据库,各请求复用连接,不要每次请求都重新连接。
❓ 知识问答
Q1:为什么脚本报错说连接被拒绝?
先检查 mongo-demo 容器是否在运行(docker ps),再确认连接串里的账号、密码、端口和启动容器时设置的一致。容器没启动或密码写错都会导致连接失败。
Q2:client.db() 和 db.collection() 会访问数据库吗?
不会。这两步只是拿到操作入口,真正的网络请求发生在 connect()、find()、insertOne() 这些操作上。所以即使数据库或集合还不存在,这两行也不会报错。
Q3:为什么用字符串形式的 _id 查询返回 null?
MongoDB 的 _id 是 ObjectId 类型,和字符串不相等。要从 mongodb 包引入 ObjectId,写成 findOne({ _id: new ObjectId(id) })。
Q4:每次操作完都要调用 client.close() 吗?
一次性脚本需要,否则进程不会退出。Express 这类长期运行的服务则相反,只在启动时连接一次并持续复用,进程关闭时才需要关闭连接。
Q5:密码写在代码里有什么风险?
代码一旦提交到 Git 仓库或被他人看到,数据库密码就泄露了。真实项目要用环境变量管理,并确保 .env 文件在 .gitignore 中。
🧪 小练习
练习一:参照本文示例,写一个 getUserByName 函数,接收用户名参数,返回对应的用户文档,查不到时返回 null。
const { MongoClient } = require('mongodb');
const url = 'mongodb://root:123456@localhost:27017';
const client = new MongoClient(url);
async function getUserByName(username) {
// 请在这里编写代码
}
练习二:给 Express 服务再加一个 GET /users/:name 接口,根据路径中的用户名查询单个用户并返回 JSON。
app.get('/users/:name', async (req, res) => {
// 请在这里编写代码
});
🎉 恭喜你已经掌握在 Node.js 中操作 MongoDB 的技能啦!下一篇我们学习 Mongoose,它能给集合加上字段约束和校验,让数据操作更规范、更省心。
