Mongoose 教程

项目概述

Mongoose 是 MongoDB 的对象建模工具,为 Node.js 环境设计,提供了一种优雅的方式来处理 MongoDB 数据库操作。它允许开发者定义数据模式、验证数据、处理关联关系等,大幅简化了 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);
  }
};

代码优化建议

  1. 使用 async/await:优先使用 async/await 语法,避免回调地狱
  2. 错误处理:始终使用 try/catch 块处理异步操作的错误
  3. 索引优化:为频繁查询的字段创建索引,提高查询性能
  4. 批量操作:对于大量数据操作,使用批量操作 API,减少数据库请求次数
  5. 连接管理:在应用启动时建立数据库连接,在应用关闭时关闭连接
  6. 避免过度使用 populate:populate 操作会增加数据库查询,对于复杂关联,考虑使用其他方式处理
  7. 验证逻辑:将复杂的验证逻辑封装为自定义验证器,提高代码可读性
  8. 中间件顺序:注意中间件的执行顺序,确保它们按照预期执行

参考资源

« 上一篇 Prisma 教程 - 现代的 Node.js 和 TypeScript ORM 下一篇 » Redis 教程 - 高性能内存数据结构存储