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)。这里以本地开发为例。
- 确保 MongoDB 服务已在后台运行(Windows 用户可检查服务列表,macOS/Linux 用户可运行
mongod命令)。 - 在项目根目录创建
app.js文件。 - 写入以下代码连接本地数据库:
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);
暂无评论,快来抢沙发吧!