MongoDB 是最流行的 NoSQL 数据库之一,以文档(Document)形式存储数据,与 Node.js 结合非常紧密。本章将介绍如何使用 Mongoose ODM(对象文档映射器)在 Node.js 应用中连接和操作 MongoDB,从安装到高级查询,帮助你快速掌握数据持久化技能。
MongoDB 是一个基于分布式文件存储的数据库,使用 BSON(类 JSON)格式存储数据。它的主要特点包括:
在 Node.js 生态中,mongoose 是操作 MongoDB 最常用的库,它提供了 Schema 验证、中间件、虚拟属性等方便的功能。
在开始之前,请确保你的系统已安装 MongoDB 数据库,并启动服务。可以从 MongoDB 官网 下载安装。或者使用云数据库(如 MongoDB Atlas)获取连接字符串。
创建项目目录并初始化 package.json:
mkdir node-mongodb
cd node-mongodb
npm init -y
Mongoose 是一个优雅的 MongoDB ODM,它提供了 Schema 验证、中间件等功能。安装:
npm install mongoose
同时可以安装 dotenv 管理环境变量(可选):
npm install dotenv
在项目根目录创建 .env 文件,存放数据库连接字符串:
MONGODB_URI=mongodb://localhost:27017/mydb
然后创建 db.js 或直接在应用入口连接:
const mongoose = require('mongoose');
require('dotenv').config();
const connectDB = async () => {
try {
await mongoose.connect(process.env.MONGODB_URI, {
useNewUrlParser: true,
useUnifiedTopology: true,
});
console.log('MongoDB 连接成功');
} catch (err) {
console.error('MongoDB 连接失败:', err.message);
process.exit(1);
}
};
module.exports = connectDB;
在 app.js 或 server.js 中调用:
const express = require('express');
const connectDB = require('./db');
const app = express();
connectDB();
// ... 其他中间件和路由
useNewUrlParser 和 useUnifiedTopology 是 Mongoose 推荐的选项,用于解决旧的连接解析器问题。
在 Mongoose 中,一切从 Schema 开始。Schema 定义了文档的结构、默认值、验证器等。然后通过 mongoose.model 方法编译成 Model。
创建一个 models/User.js 文件:
const mongoose = require('mongoose');
const userSchema = new mongoose.Schema({
name: {
type: String,
required: [true, '姓名不能为空'],
trim: true,
maxlength: [50, '姓名不能超过50个字符']
},
email: {
type: String,
required: [true, '邮箱不能为空'],
unique: true,
lowercase: true,
match: [/^\S+@\S+\.\S+$/, '请提供有效的邮箱地址']
},
age: {
type: Number,
min: [0, '年龄不能小于0'],
max: [120, '年龄不能超过120']
},
role: {
type: String,
enum: ['user', 'admin'],
default: 'user'
},
createdAt: {
type: Date,
default: Date.now
}
});
module.exports = mongoose.model('User', userSchema);
这里我们定义了一个 User 模型,包含了常见的字段和验证规则。注意 unique 选项不是验证器,而是用于创建数据库索引。
有了 Model,就可以进行增删改查操作。以下示例均使用 async/await 语法。
const User = require('./models/User');
const createUser = async (userData) => {
try {
const user = new User(userData);
const savedUser = await user.save();
console.log('用户创建成功:', savedUser);
return savedUser;
} catch (err) {
console.error('创建用户失败:', err.message);
}
};
// 调用
createUser({ name: '张三', email: 'zhangsan@example.com', age: 25 });
也可以使用 User.create(userData) 直接创建并保存。
查找所有用户:
const getAllUsers = async () => {
const users = await User.find();
console.log(users);
};
条件查询:
const getAdults = async () => {
const adults = await User.find({ age: { $gte: 18 } });
console.log(adults);
};
查找单个用户(通过 ID):
const getUserById = async (id) => {
const user = await User.findById(id);
console.log(user);
};
查询一条记录:
const getUserByEmail = async (email) => {
const user = await User.findOne({ email });
console.log(user);
};
通过 ID 更新:
const updateUser = async (id, updateData) => {
const user = await User.findByIdAndUpdate(id, updateData, {
new: true, // 返回更新后的文档
runValidators: true // 运行验证器
});
console.log('更新后的用户:', user);
return user;
};
或者先查找再修改:
const user = await User.findById(id);
user.name = '新名字';
await user.save();
const deleteUser = async (id) => {
const result = await User.findByIdAndDelete(id);
if (result) {
console.log('用户删除成功');
} else {
console.log('用户不存在');
}
};
也可使用 deleteOne 或 deleteMany。
MongoDB 提供了丰富的查询操作符和聚合管道,用于复杂数据分析。
$lt, $lte, $gt, $gte:比较运算符$in, $nin:在/不在数组中$or, $and:逻辑运算$regex:正则表达式// 查询年龄在20到30之间的用户
const users = await User.find({ age: { $gte: 20, $lte: 30 } });
// 查询名字包含 '张' 的用户
const users = await User.find({ name: { $regex: '张', $options: 'i' } });
聚合框架用于数据统计和转换。例如,统计每个角色的用户数量:
const stats = await User.aggregate([
{ $group: { _id: '$role', count: { $sum: 1 } } }
]);
console.log(stats); // [ { _id: 'user', count: 10 }, { _id: 'admin', count: 2 } ]
更复杂的聚合可以包括 $match、$project、$lookup(关联)等阶段。
Mongoose 内置了丰富的验证器,如 required、min/max、enum、match 等。你也可以编写自定义验证器:
const userSchema = new mongoose.Schema({
phone: {
type: String,
validate: {
validator: function(v) {
return /\d{11}/.test(v);
},
message: props => `${props.value} 不是有效的11位手机号`
}
}
});
为了提高查询性能,可以在 Schema 中定义索引:
userSchema.index({ email: 1 }, { unique: true }); // 创建唯一索引
userSchema.index({ age: -1 }); // 降序索引
或者在字段定义中使用 index: true。索引可以在应用启动时自动创建(需要设置 autoIndex,生产环境建议手动管理)。
MongoDB 本身不是关系型数据库,但 Mongoose 提供了 populate 方法来实现类似外键的关联。例如,一个文章(Post)属于一个用户:
const postSchema = new mongoose.Schema({
title: String,
content: String,
author: { type: mongoose.Schema.Types.ObjectId, ref: 'User' }
});
const Post = mongoose.model('Post', postSchema);
// 查询文章并填充作者信息
const posts = await Post.find().populate('author');
console.log(posts[0].author.name); // 作者姓名
Mongoose 中间件(也称为钩子)可以在某些操作(如 save、find)前后执行逻辑。例如,在保存前对密码进行哈希:
userSchema.pre('save', async function(next) {
// this 指向当前文档
if (this.isModified('password')) {
this.password = await bcrypt.hash(this.password, 10);
}
next();
});
将数据库连接字符串、密码等敏感信息存储在环境变量中(如 .env),并使用 dotenv 加载。示例:
require('dotenv').config();
const mongoose = require('mongoose');
mongoose.connect(process.env.MONGODB_URI, {
useNewUrlParser: true,
useUnifiedTopology: true
});
下面是一个使用 Express + Mongoose 的简单 REST API 示例:
const express = require('express');
const connectDB = require('./db');
const User = require('./models/User');
const app = express();
connectDB();
app.use(express.json());
// 创建用户
app.post('/users', async (req, res) => {
try {
const user = new User(req.body);
await user.save();
res.status(201).json(user);
} catch (err) {
res.status(400).json({ error: err.message });
}
});
// 获取所有用户
app.get('/users', async (req, res) => {
try {
const users = await User.find();
res.json(users);
} catch (err) {
res.status(500).json({ error: err.message });
}
});
// 获取单个用户
app.get('/users/:id', async (req, res) => {
try {
const user = await User.findById(req.params.id);
if (!user) return res.status(404).json({ error: '用户不存在' });
res.json(user);
} catch (err) {
res.status(500).json({ error: err.message });
}
});
// 更新用户
app.put('/users/:id', async (req, res) => {
try {
const user = await User.findByIdAndUpdate(req.params.id, req.body, {
new: true,
runValidators: true
});
if (!user) return res.status(404).json({ error: '用户不存在' });
res.json(user);
} catch (err) {
res.status(400).json({ error: err.message });
}
});
// 删除用户
app.delete('/users/:id', async (req, res) => {
try {
const user = await User.findByIdAndDelete(req.params.id);
if (!user) return res.status(404).json({ error: '用户不存在' });
res.json({ message: '用户删除成功' });
} catch (err) {
res.status(500).json({ error: err.message });
}
});
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => console.log(`Server running on port ${PORT}`));
mongoose.set('debug', true);。migrate-mongo)。