HOWTO · MongoDB

MongoDB 和 Mongoose 的区别

在本初学者课程中,我们将向你展示如何区分 MongoDB 和 Mongoose。本文还将讨论每种方法的好处。

本页内容

MongoDB 是文档数据库;Mongoose 是供使用 MongoDB 的 Node.js 应用选择使用的 JavaScript 对象数据建模(ODM)库。它们处于不同层级,并非相互替代的选项。在 Node.js 中使用 MongoDB 时,可以直接使用官方的 MongoDB Node.js 驱动程序,也可以使用基于该驱动程序并增添建模功能的 Mongoose。

MongoDB 和 Mongoose 分别是什么

MongoDB 将记录存储为集合中的 BSON 文档。同一集合中的文档可以包含不同字段,因此,应用可以随着需求变化调整数据结构,而不必让每条记录都符合固定的关系型表定义。这种灵活性并不表示数据没有结构或无法检查:应用可以实施规则,MongoDB 也支持在数据库层设置验证规则。

Mongoose 是一个 Node.js 库,供开发者通过模式描述应用数据,并使用模型操作数据。模式可以指定预期字段、类型、默认值和验证规则。模型提供创建、查找、更新和删除 MongoDB 集合中文档的方法。Mongoose 属于 ODM,因为它负责在 JavaScript 对象与已存储文档之间进行映射;它本身并不是数据库。

MongoDB Node.js 驱动程序是另一种选择。它将 Node.js 应用连接到 MongoDB,并提供数据库、集合和文档操作。Mongoose 在底层使用该驱动程序,同时添加自己的模式和模型约定。无论采用哪种方式,最终操作的都是 MongoDB 文档。

MongoDB 和 Mongoose 如何协同工作

它们之间的关系是:Node.js 应用可以直接调用 MongoDB Node.js 驱动程序,也可以调用 Mongoose;Mongoose 通过驱动程序与 MongoDB 通信;MongoDB 则负责存储并返回文档。无论采用哪种方式,都需要部署 MongoDB 并建立连接。添加 Mongoose 并不会取代数据库,也不会免去选择数据库和集合的步骤。

从根本上说,存储的文档由字段和值组成。例如,客户记录可能包含姓名和电子邮件地址。在应用代码中,可以用 JavaScript 对象表示这种结构:

运行以下示例:

本节中的代码以及显示的标签或输出仍为英文。

const customer = { name: "Ada", email: "ada@example.com" };
console.log(`${customer.name}: ${customer.email}`);
Ada: ada@example.com

这个示例已成功运行,只展示了文档在 JavaScript 中的结构;它没有连接到 MongoDB,也没有调用任何一个库。下面的代码片段展示两种 API 用法。示例假设已安装 mongodb 或 mongoose 软件包、MongoDB 部署可供连接,并由调用方提供连接 URI。这些代码仅用于说明,并未通过相应软件包或数据库执行。

使用 MongoDB Node.js 驱动程序时,先选择数据库和集合,再直接调用集合操作:

async function insertCustomer(uri) {
  const { MongoClient } = require("mongodb");
  const client = new MongoClient(uri);
  const customer = { name: "Ada", email: "ada@example.com" };

  try {
    await client.connect();
    return await client.db("app").collection("customers").insertOne(customer);
  } finally {
    await client.close();
  }
}

使用 Mongoose 时,定义模式、创建模型,然后调用模型操作:

async function createCustomer(uri) {
  const mongoose = require("mongoose");
  const customerSchema = new mongoose.Schema({ name: String, email: String });
  const Customer = mongoose.model("Customer", customerSchema);

  try {
    await mongoose.connect(uri);
    return await Customer.create({ name: "Ada", email: "ada@example.com" });
  } finally {
    await mongoose.disconnect();
  }
}

这些示例并不表示实际运行过数据库操作。驱动程序示例会明确打开并关闭客户端;Mongoose 示例则通过 Mongoose 连接和断开连接。两种情况下,调用方都需要提供有效的 URI,并处理返回结果或可能出现的连接错误。

MongoDB 和 Mongoose 的主要区别

主要区别在于两者的职责,以及应用规则所在的层级:

比较方面 MongoDB Mongoose
本质 用于存储和查询数据的文档数据库 应用使用的 Node.js ODM 库
Node.js 访问方式 驱动程序提供数据库和集合操作 模式和模型在驱动程序之上增加应用层功能
数据结构 同一集合中的文档结构可以不同 模式可以描述特定模型所期望的数据结构
验证 可以使用数据库验证、应用检查,或两者并用 可以在通过 Mongoose 保存文档前,根据模式规则验证文档
查询 使用驱动程序的集合操作和 MongoDB 查询语法 提供模型与查询构建 API,但实际操作仍由驱动程序执行

Mongoose 模式并不能保证服务器上的每次写入都遵守其中的规则。通过其他应用、数据库工具或直接使用驱动程序进行的写入,不会自动执行 Mongoose 验证。如果需要确保所有写入方都遵循一致规则,也应考虑 MongoDB 自身的验证功能,并结合应用层检查。

Mongoose 还可以根据模式类型转换值,并应用由应用配置的默认值或中间件。这些行为能提供有用的约定,但它们属于库的功能,并非 MongoDB 本身的属性。集合名称仍然需要确定:Mongoose 会将模型关联到集合,默认采用自身的命名约定,也可以在需要时显式指定集合。

何时使用 MongoDB 驱动程序,何时使用 Mongoose

如果你希望直接使用 MongoDB 的数据库和集合 API、减少依赖,或处理不断变化、差异较大的文档结构,可以直接使用 MongoDB Node.js 驱动程序。驱动程序还会明确呈现数据库操作;如果服务的数据访问层已经负责验证和统一约定,这种方式可能很合适。

如果应用需要命名模型、集中定义模式、应用层验证、类型转换、默认值或中间件,可以考虑 Mongoose。当多个路由或服务会操作相同类型的文档时,这些功能有助于组织代码。不过,开发者也需要理解并维护这些约定。

没有一种选择对所有项目来说都必然更简单或更快。请考虑文档结构有多稳定、验证应在哪个层级执行、团队希望应用直接使用多少 MongoDB API,以及项目是否确实需要 Mongoose 增加的这一层。也可以在部分数据访问路径中使用驱动程序,在其他路径中使用 Mongoose;前提是团队明确共享规则和集合的管理责任。

优势与权衡

当领域需求适合时,MongoDB 灵活的文档模型允许应用存储字段各不相同的记录。直接使用驱动程序可以省去 ODM 层,并让代码直接操作集合和 MongoDB。相应的代价是,应用团队需要自行制定并一致地执行数据验证、默认值以及对象到文档的处理约定。

Mongoose 通过模式和模型集中管理其中许多约定。它提供的验证、类型转换、默认值、中间件和查询辅助功能可以减少重复的应用代码。另一方面,开发者需要增加一个依赖,并理解 Mongoose 特有的行为,包括验证何时运行,以及模型配置如何对应到集合。模式可以改善应用内部的一致性,但它本身无法约束通过 Mongoose 以外的方式写入的数据。

无论采用哪种方式,数据库索引、访问控制、部署和运维实践仍然重要。ODM 不会自动保证数据模型正确;使用驱动程序也不妨碍应用定义清晰的模式。关键是明确每条规则由哪个层级负责,以及如何确保它应用于所有相关写入。

为你的应用选择合适的方案

两种设计中的数据库都是 MongoDB。对于 Node.js 应用,实际需要选择的通常是 MongoDB Node.js 驱动程序还是 Mongoose,而不是把 MongoDB 和 Mongoose 当作相互竞争的数据库。

如果数据访问层适合直接控制集合操作并灵活处理文档,就选择驱动程序。如果基于模式的模型和应用层功能能切实满足组织代码或验证数据的需求,就选择 Mongoose。决定之前,请确认验证必须在哪个层级执行、是否有不同写入方操作相同集合,以及团队是否愿意维护 ODM 的约定。只要职责划分明确,两种方案都可以很好地发挥作用。