mongoose学习笔记

安装mongoose

shell 复制代码
npm install mongoose --save

typescript:

不需要 单独安装 @types/mongoose,因为从 Mongoose 5.11.0 版本开始,Mongoose 官方已经内置了对 TypeScript 的支持,提供了自己的 index.d.ts 类型定义文件。

nodejs,express项目中使用mongoose

js 复制代码
const express = require('express')
const mongoose = require('mongoose')

const app = express()

const DB = 'mongodb://localhost:27017/test'
// 这里的connect方法返回一个promise
mongoose
  .connect(DB)
  .then(() => {
    console.log('MongoDB connection successfully 👍')
  })
  .catch((error) => {
    console.log(error)
  })

const port = 3000
app.listen(port, 'localhost', () => {
  console.log('App running at http://localhost:3000 👍')
})

注意点:

  • mongodb数据库的url比较特殊,他有自己的协议叫mongodb,所以要写成:
shell 复制代码
mongodb://localhost:27017/test
  • mongoose.connect()函数返回promise,所以我们可以得到成功和拒绝的状态。

开始学习mongoose

首先建一个目录,叫models,可以在这里定义要映射到数据库的代码。因为mongodb不需要提前建表(集合),所以我们在这里想怎么建就怎么建:

user.js

js 复制代码
const { Schema, model } = require('mongoose')

/**
 * 定义 Schema:
 * Schema 定义了 MongoDB 集合中文档的结构和数据类型,类似于数据库表的蓝图。
 * 不能直接用来操作数据库,但用于创建 Model。
 */
const userSchema = new Schema(
  {
    username: {
      type: String, // 注意,大写的String,不是typescript中的小写string
      required: true,
      unique: true,
    },
    password: {
      type: String,
      required: true,
    },
    email: String,
  },
  {
    timestamps: true, // 自动添加 createdAt 和 updatedAt 字段
  },
)

/**
 * 定义 Model:
 * Model 是一个基于 Schema 的构造函数,用于创建和操作 MongoDB 中的文档。
 * 每个 Model 对应 MongoDB 中的一个集合(collection)。
 */
const User = model('User', userSchema)

module.exports = User

mongoose.model('User', userSchema)这里的User是什么东西?

  • 'User' 是一个字符串,指定了 MongoDB 数据库中集合的名称。
  • Mongoose 会根据这个名称在 MongoDB 中查找或创建对应的集合。通常,Mongoose 会将模型名称转换为小写并加上复数形式(如果适用)。例如,'User' 对应的集合名称通常是 users(MongoDB 集合名称通常是小写复数)。
  • 这个集合将存储所有基于 userSchema 定义的文档(documents)。每个文档代表一个用户,包含 username、password、email 等字段。

然后插入数据:

js 复制代码
const { Router } = require('express')
const User = require('../models/user')

const router = Router()

// 添加用户
router.post('/users', async (req, res) => {
  try {
    const newUser = req.body
    // 使用 User 模型创建一个文档实例
    const user = User(newUser)
    // save函数返回一个 Promise。如果 save() 成功,Promise 会解析为保存的文档。
    const savedUser = await user.save(newUser)

    res.json({
      message: 'success',
      data: savedUser,
    })
  } catch (error) {
    res.status(400).json({
      message: 'failed',
      error: error,
    })
  }
})

module.exports = router

mongoose常用的内置函数

1️⃣ save() ------ 保存文档实例

比如上面讲过的示例:

js 复制代码
// 添加用户
router.post('/users', async (req, res) => {
  try {
    const newUser = req.body
    const user = User(newUser)
    // save函数返回一个 Promise。如果 save() 成功,Promise 会解析为保存的文档。
    const savedUser = await user.save(newUser)

    res.json({
      message: 'success',
      data: savedUser,
    })
  } catch (error) {
    res.status(400).json({
      message: 'failed',
      error: error,
    })
  }
})

要点

  • 只能在"文档实例"上调用,而不是模型本身。
  • 如果是新文档,会触发 insert;如果是已存在的,会执行 update
  • 会触发 Mongoose 中定义的中间件(middleware) (例如 pre('save')post('save'))。

2️⃣ create() ------ 直接创建并保存

官方文档:mongoose.node.org.cn/docs/api/mo...

功能:一步完成"实例化 + 保存"。

js 复制代码
// 创建用户
router.post('/users', async (req, res) => {
  try {
    // create() 相当于 new + save()
    const createdUser = await User.create(req.body)

    res.json({
      message: 'success',
      data: createdUser,
    })
  } catch (error) {
    res.status(400).json({
      message: 'failed',
      error: error,
    })
  }
})

要点

  • 推荐在直接创建新文档时使用。
  • 自动触发验证(validation)与中间件。
  • 支持批量创建:User.create([{}, {}, ...])

3️⃣ findById() ------ 根据 _id 查询

官方文档:mongoose.node.org.cn/docs/api/mo...

比如请求url:http://localhost:3000/users/68f1f4e43ad19870bd3906b1

js 复制代码
// 根据id获取用户
router.get('/users/:id', async (req, res) => {
  try {
    const user = await User.findById(req.params.id)
    // 如果用户不存在
    if (!user) return res.status(404).json({ message: '没找到这个用户' })

    res.json({ message: 'success', data: user })
  } catch (error) {
    res.status(400).json({
      message: 'failed',
      error: error,
    })
  }
})

要点

  • 自动将字符串 ID 转为 ObjectId
  • 如果 ID 无效,可能抛出 CastError

4️⃣ findOne() ------ 查询单个文档

官方文档:mongoose.node.org.cn/docs/api/mo...

js 复制代码
// 根据用户名查询用户
router.get('/users', async (req, res) => {
  try {
    const user = await User.findOne({ username: req.query.username })
    // 如果用户不存在
    if (!user) return res.status(404).json({ message: '没找到这个用户' })

    res.json({ message: 'success', data: user })
  } catch (error) {
    res.status(400).json({
      message: 'failed',
      error: error,
    })
  }
})

要点

  • 返回单个文档对象或 null
  • 如果你只需要一条结果,性能比 find() 更好。

5️⃣find() ------ 查询多个文档

官方文档:mongoose.node.org.cn/docs/api/mo...

js 复制代码
// 查询所有用户
router.get('/users', async (req, res) => {
  try {
    console.log(req)
    const users = await User.find()

    res.json({ message: 'success', data: users })
  } catch (error) {
    res.status(400).json({
      message: 'failed',
      error: error,
    })
  }
})

要点

  • 返回一个数组(即使只有一个文档)。
  • 支持条件:User.find({ age: { $gte: 18 } })
  • 可以链式调用:.sort(), .limit(), .select() 等。

6️⃣ findByIdAndUpdate() ------ 查找并更新

官方文档:mongoose.node.org.cn/docs/api/mo...

js 复制代码
// 查找并更新
router.put('/users/:id', async (req, res) => {
  try {
    console.log(req)
    const updatedUser = await User.findByIdAndUpdate(
      req.params.id,
      req.body,
      { new: true, runValidators: true } // 返回更新后的文档 + 启用验证
    )

    if (!updatedUser) return res.status(404).json({ message: 'User not found' })

    res.json({ message: 'success', data: updatedUser })
  } catch (error) {
    res.status(400).json({ message: 'failed', error: error.message })
  }
})

要点

  • { new: true } 表示返回更新后的文档。
  • { runValidators: true } 开启数据验证。
  • 不会触发 save() 中间件。

7️⃣ findByIdAndDelete() ------ 查找并删除

官方文档:mongoose.node.org.cn/docs/api/mo...

js 复制代码
// 删除用户
router.delete('/users/:id', async (req, res) => {
  try {
    const deletedUser = await User.findByIdAndDelete(req.params.id)

    if (!deletedUser) return res.status(404).json({ message: 'User not found' })

    res.json({ message: 'success', data: deletedUser })
  } catch (error) {
    res.status(400).json({ message: 'failed', error: error.message })
  }
})

要点

  • 返回被删除的文档(如果存在)。
  • 不会触发 remove()deleteOne() 的中间件。

8️⃣ countDocuments() ------ 统计数量

官方文档:mongoose.node.org.cn/docs/api/mo...

js 复制代码
router.get('/users/count', async (req, res) => {
  try {
    const count = await User.countDocuments({ age: { $gte: 18 } })
    res.json({ message: 'success', data: count })
  } catch (error) {
    res.status(500).json({ message: 'failed', error: error.message })
  }
})

要点

  • 常用于分页、统计。
  • estimatedDocumentCount() 更精确(但略慢)。

9️⃣ deleteMany() ------ 批量删除

官方文档:mongoose.node.org.cn/docs/api/mo...

js 复制代码
router.delete('/users', async (req, res) => {
  try {
    const result = await User.deleteMany({ active: false })
    res.json({ message: 'success', data: result })
  } catch (error) {
    res.status(500).json({ message: 'failed', error: error.message })
  }
})

要点

  • 返回 { acknowledged: true, deletedCount: X }
  • 不会触发 remove() 钩子。

1️⃣0️⃣ 分页查询

js 复制代码
// ✅ 分页查询所有用户
router.get('/users', async (req, res) => {
  try {
    // 从查询参数中获取分页参数
    // 前端传 ?page=2&limit=5
    const page = parseInt(req.query.page) || 1        // 当前页码,默认为 1
    const limit = parseInt(req.query.limit) || 10     // 每页条数,默认为 10

    // 计算要跳过多少条数据
    // 比如页数是2时:(2-1)*10,跳过10个,就是上面一页中的数据
    const skip = (page - 1) * limit

    // 查询用户列表,使用 skip 和 limit 实现分页
    const users = await User.find()
      .skip(skip)     // 跳过前面几条
      .limit(limit)   // 只取本页的数量
      .sort({ createdAt: -1 }) // 可选:按创建时间倒序排序

    // 获取总数,用于计算总页数
    const total = await User.countDocuments()

    res.json({
      message: 'success',
      data: users,
      pagination: {
        total,              // 总记录数
        page,               // 当前页
        limit,              // 每页条数
        totalPages: Math.ceil(total / limit), // 总页数
      },
    })
  } catch (error) {
    res.status(400).json({
      message: 'failed',
      error,
    })
  }
})

条件分页示例:

js 复制代码
const { page = 1, limit = 10, keyword = '' } = req.query
const filter = keyword ? { name: new RegExp(keyword, 'i') } : {}
const users = await User.find(filter)
  .skip((page - 1) * limit)
  .limit(limit)

时间戳

官方文档:mongoose.node.org.cn/docs/timest...

Mongoose 模式支持 timestamps 选项。如果设置 timestamps: true,Mongoose 会在你的模式中添加两个类型为 Date 的属性

  1. createdAt: 表示该文档创建时间的一个日期
  2. updatedAt: 表示该文档最后更新时间的一个日期

Mongoose 随后会在文档首次插入时设置 createdAt,并在你使用 save()updateOne()updateMany()findOneAndUpdate()update()replaceOne()bulkWrite() 更新文档时更新 updatedAt

js 复制代码
const userSchema = new Schema(
  {
    username: {
      type: String, // 注意,大写的String,不是typescript中的小写string
      required: true,
      unique: true,
    },
    password: {
      type: String,
      required: true,
    },
    email: String,
  },
  {
    timestamps: true, // 自动添加 createdAt 和 updatedAt 字段
  },
)

中间件

预处理中间件

官方文档:mongoose.nodejs.cn/docs/middle...

1. 密码加密示例

js 复制代码
const userSchema = new Schema(
  {
    username: {
      type: String, // 注意,大写的String,不是typescript中的小写string
      required: true,
      unique: true,
    },
    password: {
      type: String,
      required: true,
    },
    email: String,
  },
  {
    timestamps: true, // 自动添加 createdAt 和 updatedAt 字段
  },
)

// 在执行save操作之前执行
userSchema.pre('save', async function (next) {
  // 如果修改的不是密码,就不用管
  if (!this.isModified('password')) return next()

  // 进行加密
  this.password = await bcrypt.hash(this.password, 12)
})

解释:

js 复制代码
userSchema.pre('save', ...)
  • 这个函数会在 每次执行 user.save()User.create() 之前 自动运行。

js 复制代码
userSchema.pre('save', async function (next) {
  • 注册一个 "save" 操作的前置中间件。
  • 注意这里用的是普通函数 function(),不是箭头函数,因为:
    • Mongoose 会把 this 绑定为当前文档实例
    • 如果用箭头函数,this 就不会指向文档了。

js 复制代码
  if (!this.isModified('password')) return next()
  • this 表示当前要保存的文档对象(例如一个 User 实例)。
  • .isModified('password') 是 Mongoose 的内置方法,详情请看isModified。用来检测该字段是否被修改过。

实例方法

Models 的实例是documents。文档有很多自己的实例方法,比如我们前面用的save()。我们还可以定义自己的自定义文档实例方法。

比如:

js 复制代码
// 验证密码
userSchema.methods.correctPassword = function (candidatePassword, userPassword) {
  return bcrypt.compare(candidatePassword, userPassword)
}

// 调用
const user = new User()
user.correctPassword(两个参数)
相关推荐
植物系青年6 个月前
前端玩数据库 👏 MongoDB/Mongoose 入门指南(下)
前端·mongodb·mongoose
植物系青年6 个月前
前端玩数据库 👏 MongoDB/Mongoose 入门指南(上)
前端·mongodb·mongoose
ziyu_jia10 个月前
MongoDB、Mongoose使用教程
前端·数据库·mongodb·node.js·mongoose
花姐夫Jun10 个月前
node.js基础学习-mongoose操作MongoDB(十二)
学习·mongodb·node.js·mongoose
云牧2 年前
使用 Mongoose 在 Node 和 Nest 中操作 MongoDB 数据库
前端·node.js·mongoose
耶耶耶耶耶~2 年前
mongoose httpserver浅析
网络·mongoose·网络通讯·httpserver
耶耶耶耶耶~2 年前
mongoose httpserver webcommand
网络·mongoose·httpserver
慕仲卿2 年前
Mongoose 使用简介
node.js·mongoose