文章目录

Node.js 数据库:MongoDB 与 Mongoose

发布于 2026-04-03 07:31:26 · 浏览 9 次 · 评论 0 条

Node.js 数据库:MongoDB 与 Mongoose

在 Node.js 项目中连接数据库,MongoDB 是最常用的选择之一。它是一个 NoSQL 数据库,用 JSON 风格的文档存储数据,非常适合 JavaScript 开发者。而 Mongoose 是一个对象数据建模(ODM)库,它让你能用类似定义类的方式操作 MongoDB,还能自动校验数据格式、处理关联关系。


安装必要依赖

创建一个新的 Node.js 项目目录,并初始化:

mkdir my-node-app
cd my-node-app
npm init -y

安装 mongoose 包:

npm install mongoose

这会自动把 mongoose 添加到 package.json 的依赖中,并下载所需文件。


连接 MongoDB 数据库

MongoDB 可以本地运行,也可以使用云服务(如 MongoDB Atlas)。这里以本地开发为例。

  1. 确保 MongoDB 服务已在后台运行(Windows 用户可检查服务列表,macOS/Linux 用户可运行 mongod 命令)。
  2. 在项目根目录创建 app.js 文件。
  3. 写入以下代码连接本地数据库:
const mongoose = require('mongoose');

mongoose.connect('mongodb://localhost:27017/myappdb')
  .then(() => console.log('✅ 已连接到 MongoDB'))
  .catch(err => console.error('❌ 连接失败:', err));

这段代码尝试连接到运行在 localhost:27017 的 MongoDB 实例,并使用名为 myappdb 的数据库。如果数据库不存在,MongoDB 会在首次写入时自动创建它。


定义数据模型

Mongoose 要求你先定义“模型”(Model),再通过模型操作数据。模型基于“模式”(Schema),用于规定文档的字段和类型。

app.js 中添加用户模型定义:

const userSchema = new mongoose.Schema({
  name: { type: String, required: true },
  email: { type: String, required: true, unique: true },
  age: { type: Number, min: 0, max: 120 }
});

const User = mongoose.model('User', userSchema);
  • name 字段必须是字符串且不能为空。
  • email 必须唯一(MongoDB 会自动为此字段建索引)。
  • age 必须是数字,且在 0 到 120 之间。

插入数据

使用模型的 .create() 方法插入新文档:

async function createUser() {
  try {
    const newUser = await User.create({
      name: '张三',
      email: 'zhangsan@example.com',
      age: 28
    });
    console.log('👤 新用户已创建:', newUser);
  } catch (err) {
    console.error('⚠️ 创建失败:', err.message);
  }
}

createUser();

运行 node app.js,如果一切正常,控制台会打印新用户的完整信息(包括自动生成的 _id)。


查询数据

Mongoose 提供多种查询方法。以下是几个常用示例:

查找所有用户

const allUsers = await User.find();
console.log('👥 所有用户:', allUsers);

按条件查找(例如找年龄大于 25 的用户):

const adults = await User.find({ age: { $gt: 25 } });
console.log('🧑‍🦳 年龄大于 25 的用户:', adults);
```

**只返回特定字段**(例如只要名字和邮箱):

```javascript
const namesOnly = await User.find({}, 'name email');
console.log('📛 用户名与邮箱:', namesOnly);
```

**查找单个用户并更新**:

```javascript
const updatedUser = await User.findOneAndUpdate(
  { email: 'zhangsan@example.com' },
  { age: 29 },
  { new: true } // 返回更新后的文档
);
console.log('🔄 更新后的用户:', updatedUser);
```

---

## 删除数据

**删除匹配条件的文档**:

```javascript
const result = await User.deleteOne({ email: 'zhangsan@example.com' });
console.log('🗑️ 删除结果:', result.deletedCount === 1 ? '成功' : '未找到');
```

注意:`deleteOne()` 只删第一条匹配项;若要删除多个,用 `deleteMany()`。

---

## 处理连接关闭

长时间运行的应用(如 Web 服务器)通常保持数据库连接。但如果是脚本任务,应在操作完成后**显式关闭连接**:

```javascript
// 在所有异步操作结束后
await mongoose.connection.close();
console.log('🔌 数据库连接已关闭');
```

---

## 使用环境变量管理连接字符串

硬编码数据库地址不安全也不灵活。**改用环境变量**:

1. **安装** `dotenv` 包:

```bash
npm install dotenv
```

2. **在项目根目录创建** `.env` 文件:

```env
MONGODB_URI=mongodb://localhost:27017/myappdb
```

3. **在 `app.js` 顶部加载**环境变量:

```javascript
require('dotenv').config();
const mongoose = require('mongoose');

mongoose.connect(process.env.MONGODB_URI)
  .then(() => console.log('✅ 已连接到 MongoDB'))
  .catch(err => console.error('❌ 连接失败:', err));
```

现在连接字符串从外部注入,便于在不同环境(开发、测试、生产)切换。

---

## 错误处理最佳实践

Mongoose 操作可能因网络、验证失败或重复键等原因出错。**始终用 try/catch 包裹异步操作**,并检查错误类型:

```javascript
try {
  await User.create({ name: '', email: 'invalid' }); // 故意触发验证错误
} catch (err) {
  if (err.name === 'ValidationError') {
    console.error('❗ 数据验证失败:');
    for (const field in err.errors) {
      console.error(`  ${field}: ${err.errors[field].message}`);
    }
  } else if (err.code === 11000) {
    console.error('❗ 邮箱已存在');
  } else {
    console.error('❗ 未知错误:', err.message);
  }
}
```

常见错误类型:
- `ValidationError`:字段不符合 Schema 规则。
- `MongoServerError` 且 `code === 11000`:违反唯一索引约束。

---

## 性能与索引优化

当数据量增大时,查询可能变慢。**为常用查询字段添加索引**:

```javascript
const userSchema = new mongoose.Schema({
  name: { type: String, required: true },
  email: { type: String, required: true, unique: true },
  age: { type: Number, min: 0, max: 120 }
});

// 为 name 字段创建普通索引(升序)
userSchema.index({ name: 1 });

// 为 age 和 name 组合创建复合索引
userSchema.index({ age: 1, name: 1 });
```

MongoDB 会在后台自动构建这些索引。可通过 `db.users.getIndexes()`(在 mongo shell 中)查看当前集合的索引。

---

## 事务支持(高级用法)

MongoDB 4.0+ 支持多文档事务。Mongoose 通过会话(Session)实现:

```javascript
const session = await mongoose.startSession();
session.startTransaction();

try {
  const user = await User.create([{ name: '李四', email: 'lisi@test.com' }], { session });
  // 假设有另一个模型 Order
  // await Order.create([{ userId: user[0]._id, product: '书' }], { session });
  
  await session.commitTransaction();
  console.log('✅ 事务提交成功');
} catch (err) {
  await session.abortTransaction();
  console.error('❌ 事务回滚:', err.message);
} finally {
  session.endSession();
}
```

事务适用于需要保证多个操作“全成功或全失败”的场景,但会带来性能开销,应谨慎使用。

---

## 常见问题排查表

| 现象 | 可能原因 | 解决方案 |
| :--- | :--- | :--- |
| `connect ECONNREFUSED 127.0.0.1:27017` | MongoDB 服务未启动 | 启动 `mongod` 服务 |
| `Authentication failed` | 连接字符串缺少用户名/密码 | 检查 URI 格式:`mongodb://user:pass@host/db` |
| 插入时报 `E11000 duplicate key error` | 违反唯一索引(如重复邮箱) | 确保字段值唯一,或捕获错误提示用户 |
| 查询返回空数组 | 条件不匹配或字段名拼写错误 | 检查字段名是否与 Schema 一致(区分大小写) |
| 修改后数据未保存 | 忘记调用 `.save()` 或使用了不可变操作 | 使用 `findOneAndUpdate` 或确保调用 `doc.save()` |

---

## 完整示例代码

将以下内容保存为 `app.js`,即可运行一个包含增删改查的完整演示:

```javascript
require('dotenv').config();
const mongoose = require('mongoose');

const userSchema = new mongoose.Schema({
  name: { type: String, required: true },
  email: { type: String, required: true, unique: true },
  age: { type: Number, min: 0, max: 120 }
});

const User = mongoose.model('User', userSchema);

async function main() {
  await mongoose.connect(process.env.MONGODB_URI || 'mongodb://localhost:27017/myappdb');

  // 清空测试数据
  await User.deleteMany({});

  // 创建
  const user1 = await User.create({ name: '王五', email: 'wangwu@test.com', age: 30 });
  console.log('✅ 创建:', user1.name);

  // 查询
  const users = await User.find({ age: { $gte: 25 } });
  console.log('🔍 查询结果:', users.map(u => u.name));

  // 更新
  await User.updateOne({ email: 'wangwu@test.com' }, { age: 31 });
  console.log('✏️ 年龄已更新');

  // 删除
  await User.deleteOne({ email: 'wangwu@test.com' });
  console.log('🗑️ 用户已删除');

  await mongoose.connection.close();
}

main().catch(console.error);

评论 (0)

暂无评论,快来抢沙发吧!

扫一扫,手机查看

扫描上方二维码,在手机上查看本文