Mongoose 教程
项目概述
Mongoose 是 MongoDB 的对象建模工具,为 Node.js 环境设计,提供了一种优雅的方式来处理 MongoDB 数据库操作。它允许开发者定义数据模式、验证数据、处理关联关系等,大幅简化了 MongoDB 的使用。
- 项目链接:https://github.com/Automattic/mongoose
- 官方网站:https://mongoosejs.com/
- GitHub Stars:26k+
- 适用环境:Node.js 项目、MongoDB
安装设置
基本安装
使用 npm 或 yarn 安装 Mongoose:
# 使用 npm
npm install mongoose
# 使用 yarn
yarn add mongoose连接 MongoDB
在应用中连接到 MongoDB 数据库:
const mongoose = require('mongoose');
// 连接到本地 MongoDB 数据库
mongoose.connect('mongodb://localhost:27017/myapp', {
useNewUrlParser: true,
useUnifiedTopology: true
})
.then(() => console.log('MongoDB 连接成功'))
.catch(err => console.error('MongoDB 连接失败:', err));核心功能
1. Schema 定义
Mongoose 使用 Schema 来定义文档的结构:
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
// 定义用户 Schema
const userSchema = new Schema({
name: {
type: String,
required: true
},
email: {
type: String,
required: true,
unique: true
},
age: {
type: Number,
min: 0
},
createdAt: {
type: Date,
default: Date.now
}
});
// 创建 User 模型
const User = mongoose.model('User', userSchema);2. 模型操作
使用模型进行 CRUD 操作:
// 创建文档
const createUser = async () => {
try {
const user = new User({
name: '张三',
email: 'zhangsan@example.com',
age: 25
});
const savedUser = await user.save();
console.log('创建用户成功:', savedUser);
} catch (err) {
console.error('创建用户失败:', err);
}
};
// 查询文档
const findUsers = async () => {
try {
// 查询所有用户
const users = await User.find();
console.log('所有用户:', users);
// 根据条件查询
const user = await User.findOne({ name: '张三' });
console.log('查询用户:', user);
} catch (err) {
console.error('查询用户失败:', err);
}
};
// 更新文档
const updateUser = async () => {
try {
const updatedUser = await User.updateOne(
{ name: '张三' },
{ $set: { age: 26 } }
);
console.log('更新用户成功:', updatedUser);
} catch (err) {
console.error('更新用户失败:', err);
}
};
// 删除文档
const deleteUser = async () => {
try {
const deletedUser = await User.deleteOne({ name: '张三' });
console.log('删除用户成功:', deletedUser);
} catch (err) {
console.error('删除用户失败:', err);
}
};3. 关联关系
Mongoose 支持多种类型的关联关系:
// 定义帖子 Schema
const postSchema = new Schema({
title: String,
content: String,
author: {
type: Schema.Types.ObjectId,
ref: 'User'
}
});
const Post = mongoose.model('Post', postSchema);
// 创建带有关联的文档
const createPost = async () => {
try {
const user = await User.findOne({ name: '张三' });
const post = new Post({
title: '第一篇文章',
content: '这是一篇测试文章',
author: user._id
});
const savedPost = await post.save();
console.log('创建帖子成功:', savedPost);
} catch (err) {
console.error('创建帖子失败:', err);
}
};
// 查询带有关联的文档
const findPostWithAuthor = async () => {
try {
const post = await Post.findOne({ title: '第一篇文章' }).populate('author');
console.log('帖子及其作者:', post);
} catch (err) {
console.error('查询帖子失败:', err);
}
};4. 中间件
Mongoose 支持文档中间件和查询中间件:
// 文档中间件 - 保存前执行
userSchema.pre('save', function(next) {
// 可以在这里进行数据处理,如密码加密
console.log('保存用户前执行');
next();
});
// 文档中间件 - 保存后执行
userSchema.post('save', function(doc, next) {
console.log('保存用户后执行:', doc);
next();
});5. 验证
Mongoose 提供了强大的数据验证功能:
const userSchema = new Schema({
name: {
type: String,
required: true,
minlength: 2,
maxlength: 50
},
email: {
type: String,
required: true,
unique: true,
validate: {
validator: function(v) {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v);
},
message: props => `${props.value} 不是有效的邮箱地址!`
}
},
age: {
type: Number,
min: [0, '年龄不能为负数'],
max: [150, '年龄不能超过150']
}
});高级功能
1. 虚拟属性
虚拟属性是模型上的计算属性,不会存储在数据库中:
userSchema.virtual('fullName').get(function() {
return `${this.firstName} ${this.lastName}`;
});
userSchema.virtual('fullName').set(function(name) {
const [firstName, lastName] = name.split(' ');
this.firstName = firstName;
this.lastName = lastName;
});2. 索引
Mongoose 支持创建索引以提高查询性能:
// 单字段索引
userSchema.index({ email: 1 });
// 复合索引
userSchema.index({ firstName: 1, lastName: 1 });
// 唯一索引
userSchema.index({ email: 1 }, { unique: true });3. 聚合管道
Mongoose 支持 MongoDB 的聚合管道操作:
const aggregateUsers = async () => {
try {
const result = await User.aggregate([
{ $match: { age: { $gte: 18 } } },
{ $group: { _id: '$age', count: { $sum: 1 } } },
{ $sort: { count: -1 } }
]);
console.log('聚合结果:', result);
} catch (err) {
console.error('聚合操作失败:', err);
}
};实际应用场景
1. 用户认证系统
使用 Mongoose 构建用户认证系统:
const bcrypt = require('bcrypt');
const userSchema = new Schema({
username: {
type: String,
required: true,
unique: true
},
password: {
type: String,
required: true,
select: false
}
});
// 密码加密中间件
userSchema.pre('save', async function(next) {
if (this.isModified('password')) {
this.password = await bcrypt.hash(this.password, 10);
}
next();
});
// 密码验证方法
userSchema.methods.comparePassword = async function(candidatePassword) {
return await bcrypt.compare(candidatePassword, this.password);
};
const User = mongoose.model('User', userSchema);
// 登录验证
const login = async (username, password) => {
try {
const user = await User.findOne({ username }).select('+password');
if (!user) {
return { success: false, message: '用户不存在' };
}
const isMatch = await user.comparePassword(password);
if (!isMatch) {
return { success: false, message: '密码错误' };
}
return { success: true, user };
} catch (err) {
console.error('登录失败:', err);
return { success: false, message: '登录失败' };
}
};2. 博客系统
使用 Mongoose 构建博客系统:
// 定义评论 Schema
const commentSchema = new Schema({
content: String,
author: {
type: Schema.Types.ObjectId,
ref: 'User'
},
post: {
type: Schema.Types.ObjectId,
ref: 'Post'
},
createdAt: {
type: Date,
default: Date.now
}
});
const Comment = mongoose.model('Comment', commentSchema);
// 定义帖子 Schema
const postSchema = new Schema({
title: String,
content: String,
author: {
type: Schema.Types.ObjectId,
ref: 'User'
},
comments: [{
type: Schema.Types.ObjectId,
ref: 'Comment'
}],
tags: [String],
createdAt: {
type: Date,
default: Date.now
},
updatedAt: {
type: Date,
default: Date.now
}
});
// 更新时间中间件
postSchema.pre('save', function(next) {
this.updatedAt = new Date();
next();
});
const Post = mongoose.model('Post', postSchema);
// 查询带有关联的帖子
const findPostWithDetails = async () => {
try {
const post = await Post.findOne({ title: '第一篇文章' })
.populate('author')
.populate('comments')
.populate('comments.author');
console.log('帖子详情:', post);
} catch (err) {
console.error('查询帖子失败:', err);
}
};代码优化建议
- 使用 async/await:优先使用 async/await 语法,避免回调地狱
- 错误处理:始终使用 try/catch 块处理异步操作的错误
- 索引优化:为频繁查询的字段创建索引,提高查询性能
- 批量操作:对于大量数据操作,使用批量操作 API,减少数据库请求次数
- 连接管理:在应用启动时建立数据库连接,在应用关闭时关闭连接
- 避免过度使用 populate:populate 操作会增加数据库查询,对于复杂关联,考虑使用其他方式处理
- 验证逻辑:将复杂的验证逻辑封装为自定义验证器,提高代码可读性
- 中间件顺序:注意中间件的执行顺序,确保它们按照预期执行