对于 AI 代理:可在 https://www.mongodb.com/zh-cn/docs/llms.txt 获取文档索引—通过在任何 URL 路径后添加 .md 可获取所有页面的 Markdown 版本。

教程: Mongoose入门

Mongoose是一个适用于MongoDB的对象数据建模 (ODM) 库。您可以使用Mongoose来帮助进行数据建模、模式实施、模型验证和一般数据操作。由于MongoDB具有灵活数据模型,允许您将来更改和更新数据库,因此不存在严格的模式。但是, Mongoose从一开始就强制执行半刚性模式,因此您必须定义模式和模型。

模式定义集合文档的结构。Mongoose模式直接映射到MongoDB集合。

以下示例创建了一个名为 blog 的新模式:

const blog = new Schema( { title: String, slug: String, published: Boolean, author: String, content: String, tags: [String], comments: [ { user: String, content: String, votes: Number, }, ], }, { timestamps: true, });

创建模式时,必须定义每个字段及其数据类型。允许使用以下类型:

  • 字符串

  • 数值

  • Date

  • Buffer

  • 布尔

  • 混合

  • ObjectId

  • 阵列

  • Int32

  • Decimal128

  • double

  • Map

模型采用您的模式并将其应用其集合中的每个文档。模型负责所有文档交互,例如创建、读取、更新和删除(增删改查) 操作。

提示

您传递给模型的第一个参数是集合名称的单数形式。Mongoose会自动将其更改为复数形式,转换为小写形式,并将其用作数据库集合名称。

以下示例展示如何声明使用 blog模式的名为 Blog 的模型:

const Blog = mongoose.model('Blog', blog);

在前面的示例中,Blog 转换为MongoDB中的 blogs集合。

在本教程中,您将执行以下操作:

  • 设置环境以使用Mongoose

  • 连接至 MongoDB

  • 创建Mongoose模式和模型

  • 插入、更新、查找和删除数据

  • 项目文档字段

  • 验证数据

  • 使用多个模式和中间件

1

在开始本教程之前,请确保您已准备好以下组件:

  • 已配置集群的MongoDB Atlas帐户。要查看说明,请参阅MongoDB 指南。

  • Node.js v16.20.1或更高版本。

2

本教程使用nodemon在本地运行代码。在终端中运行以下命令以初始化项目并安装必要的依赖项:

mkdir mongodb-mongoosecd mongodb-mongoosenpm init -ynpm i mongoosenpm i -D nodemon

在首选代码编辑器中打开项目。本教程使用 ES 模块而不是 CommonJS。必须添加 module 类型才能使用 ES 模块。Gopackage.json文件并添加以下代码:

..."scripts": { "dev": "nodemon index.js"},"type": "module",...

通过运行以下命令,使用 nodemon 启动应用程序:

npm run dev

注意

使用 nodemon 时,每次保存文件时都会运行代码。

3

在项目的根级别中,创建一个名为 index.js 的文件,并将以下代码添加到文件顶部:

import mongoose from 'mongoose'; mongoose.connect("<connection string>")

将 <connection string> 占位符替换为MongoDB Atlas连接字符串。 有关如何查找连接字符串的更多信息,请参阅Atlas文档中的连接到集群教程。

4

在开始在MongoDB中添加和更新数据之前,您必须创建模式和模型。

使用Mongoose ,您可以为所需的每个模式创建一个模式模型文件。首先,创建一个名为 model 的文件夹来放置所有模式文件,然后创建您的第一个模式文件并将其命名为 Blog.js。打开此文件并添加以下代码:

import mongoose from 'mongoose';const { Schema, model } = mongoose; const blog = new Schema( { title: String, slug: String, published: Boolean, author: String, content: String, tags: [String], comments: [ { user: String, content: String, votes: Number, }, ], }, { timestamps: true, }); const Blog = model('Blog', blog);export default Blog;

此模式启用 timestamps 选项,将 Mongoose 托管的 createdAt 和 updatedAt 字段添加到自动更新的模式中。有关更多信息,请参阅Mongoose文档中的时间戳页面。

5

您现在已经设立了第一个模型和模式,可以开始向数据库插入数据。以下部分向您展示如何使用Mongoose执行增删改查操作。

Goindex.js 并将以下导入声明添加到文件顶部:

import Blog from './model/Blog.js';

在包含连接字符串的行下方添加以下代码:

// Creates a new blog post and inserts it into the databaseconst article = await Blog.create({ title: 'Awesome Post!', slug: 'awesome-post', published: true, content: 'This is the best post ever', tags: ['featured', 'announcement'],});console.log('Created article:', article);

前面的代码使用Mongoose create() 方法实例化表示博客文章的对象,然后将其保存到数据库中。返回的文档记录到控制台,包括其 _id。请记下此 _id,以便在后续步骤中使用。

要更新数据,您可以使用Mongoose直接编辑本地对象。然后,您可以使用 save() 方法将更新写入数据库。

添加以下代码以更新在上一节中插入的文章:

// Updates the title of the articlearticle.title = 'The Most Awesomest Post!!';await article.save();console.log('Updated Article:', article);

要查找特定文档,可以使用Mongoose findById() 方法通过指定文档的 _id 来查询文档。

将以下代码添加到 index.js文件,以通过 _id 查找特定文章:

// Finds the article by its ID. Replace <object id> with the objectId of the article.const articleFound = await Blog.findById('<object id>').exec();console.log('Found Article by ID:', articleFound);

注意

默认下, Mongoose查询返回 thenables,它们是具有JavaScript Promise 某些属性的对象。您可以将 exec() 方法附加到查询中以接收真正的JavaScript Promise。要学习;了解有关在Mongoose中使用 Promise 的更多信息,请参阅Mongoose文档中的Promise指南。

您可以使用Mongoose仅项目您需要的字段。以下代码指定仅项目title、slug 和 content 字段。

将以下代码添加到 index.js文件中,将 <object id> 占位符替换为之前插入的文档的 ObjectId 值:

// Finds the article by its ID and projects only the title, slug, and content fields.// Replace <object id> with the objectId of the article.const articleProject = await Blog.findById( '<object id>', 'title slug content').exec();console.log('Projected Article:', articleProject);

Mongoose使用 deleteOne() 和 deleteMany() 方法从集合中删除数据。您可以指定要删除的文档字段,并将该字段传递给您选择的方法。

将以下代码添加到索引.js文件中,以从数据库中删除一篇文章:

// Deletes one article with the slug "awesome-post".const blogOne = await Blog.deleteOne({ slug: 'awesome-post' });console.log('Deleted One Blog:', blogOne);

要删除多篇文章,可以添加以下代码:

// Deletes all articles with the slug "awesome-post".const blogMany = await Blog.deleteMany({ slug: 'awesome-post' });console.log('Deleted Many Blogs:', blogMany);
6

在前面步骤中插入的项目不包含 author、dates 或 comments 字段,即使这些字段包含在模式中。这是因为,尽管您定义了文档的结构,但尚未定义哪些字段是必填字段。任何未定义为必填字段都可以省略。

在Mongoose中,当您对字段进行验证时,必须传递一个对象作为其值。

注意

验证器会自动对 create() 和 save() 方法运行。您可以通过将 runValidators 选项设置为 true 来指定它们在更新方法(例如 update() 和 updateOne())上运行。有关验证器的更多信息,请参阅Mongoose文档中的验证页面。

您可以在Mongoose中使用多种验证方法。示例,您可以在任何您想要要求的字段上将 required设立为 true。您还可以验证类型和格式。在以下代码中,slug字段被定义为 minLength 为 4 的 string。这意味着提供少于 4 个字符的 slug 将得到 ValidationError。

要添加数据验证并定义这些要求,更新Blog.js 中的模式,如以下示例所示:

const blog = new Schema( { title: { type: String, required: true, }, slug: { type: String, required: true, minLength: 4, }, published: { type: Boolean, default: false, }, author: { type: String, required: true, }, content: String, tags: [String], comments: [ { user: String, content: String, votes: Number, }, ], }, { timestamps: true, });

添加此验证后,您的应用程序将崩溃。将 author字段添加到 index.js文件中的 create() 调用,以满足新的验证要求:

// Creates a new blog post and inserts it into databaseconst article = await Blog.create({ title: 'Awesome Post!', slug: 'awesome-post', published: true, author: 'A.B. Cee', content: 'This is the best post ever', tags: ['featured', 'announcement'],});

提示

当您将模式与Mongoose一起使用时,value: String 与 value: {type: String} 相同。

有关更多信息,请参阅Mongoose文档中的验证页面。

7

接下来,您可以通过创建另一个模式并将其嵌套在博客模式中来增加 author字段的复杂性。

在 model 文件夹中,创建一个名为 User.js 的新文件。将以下代码添加到此文件:

import mongoose from 'mongoose';const { Schema, model } = mongoose; const userSchema = new Schema({ name: { type: String, required: true, }, email: { type: String, minLength: 10, required: true, lowercase: true, },}); const User = model('User', userSchema);export default User;

要使用新的 User 模型定义博客模式中的 author字段,请使用以下更改更新Blog.js文件:

  • 将 SchemaTypes 添加到从Mongoose库提取的属性列表中。

  • 将 author字段type 更改为 SchemaTypes.ObjectId,并添加对 'User' 模型的引用 (ref)。

以下代码显示了这些更新:

const { Schema, SchemaTypes, model } = mongoose; ... // Inside Schema block: author: { type: SchemaTypes.ObjectId, ref: 'User', required: true, },...

您可以通过更改 blog 定义,为 comment.user字段重复使用相同的 User 模型:

... // Inside Schema block: comments: [{ user: { type: SchemaTypes.ObjectId, ref: 'User', required: true, },...

要在应用程序中使用新用户模型,Goindex.js文件。 将以下代码添加到文件顶部以导入用户模型:

import User from './model/User.js';

由于您在博客模式中添加了字段验证,因此之前用于插入、更新和删除博客以及指定要项目的字段的代码将无法通过验证,应用程序将出错。

通过添加以下 create() 调用来创建新用户:

// Creates a new userconst user = await User.create({ name: 'Jess Garcia', email: 'jgarcia@email.com',});

使用以下代码更新现有的 create() 调用,以创建一篇将新用户作为作者的新文章,如以下代码所示:

// Creates a new blog post that references the user as the authorconst articleAuthor = await Blog.create({ title: 'Awesome Post!', slug: 'awesome-post', author: user._id, content: 'This is the best post ever', tags: ['featured', 'announcement'],});console.log('Article with Author:', articleAuthor);

前面的代码在MongoDB 数据库中添加一个带有 blogs集合的 users集合。此代码添加必填的 author字段并将其值设置为 user._id。

要将文章的 name 和 email 字段的值添加到 author字段,您可以将Mongoose populate() 方法附加到博客查询中。populate() 方法将对 user._id 引用的用户文档执行第二次查询。

将以下代码添加到 index.js 以填充此数据:

// Populates the author field with user dataconst articlePopulate = await Blog.findOne({ title: 'Awesome Post!',}).populate('author');console.log('Article Populated:', articlePopulate);

有关详细信息,请参阅 Document.populate() Mongoose API文档页面。

8

在Mongoose中,中间件是在模式级别的异步函数执行之前或期间运行的函数。

中间件的一个示例是在保存或更新之前验证 User实例的 email字段的函数。

此示例使用 validator包。您可以通过运行以下命令来安装 validator包:

npm install validator

要添加此中间件函数,请将以下代码添加到 User.js文件的 userSchema 声明中:

import validator from 'validator';
userSchema.pre('save', function (next) { const user = this; // Normalize email if (user.email) { user.email = user.email.trim().toLowerCase(); } // Validate email format if (!validator.isEmail(user.email)) { return next(new Error('Invalid email format')); } next();});

中间件在检测到无效的电子邮件地址格式时会抛出错误。要查看此行为,请更新 index.js 文件中的 user.email 字段以使用格式错误的电子邮件地址:

const user = await User.create({ name: 'Jess Garcia', email: 'jgarciaemail.com', // Intentional error: missing '@' symbol in email address});

如果更正电子邮件解决,则可以看到未引发错误。

除了 pre() 中间件函数外, Mongoose还提供 post() 中间件函数。有关中间件的更多信息,请参阅Mongoose文档中的中间件页面。

您现在有一个示例项目,它使用Mongoose对MongoDB集合执行增删改查操作。 在这里,您可以选择使用更复杂的查询或文档验证来构建此项目。以下部分包括您可能会在应用程序中使用的Mongoose功能的一些示例。

自定义 setter 会在保存数据时修改数据,并且可以类似于验证器来实现。Mongoose提供以下自定义 setter:

  • lowercase

  • uppercase

  • trim

以下示例将 blog.slug字段中的字符小写:

const blog = new Schema( { ... slug: { type: String, required: true, minLength: 4, lowercase: true, }, ... },);

有关更多信息,请参阅 Mongoose API 文档中的 SchemaStringOptions 页面。

Mongoose包含多个从常规MongoDB方法抽象而来的辅助方法。在本节中,您可以找到其中一些方法的示例。本教程中并未专门使用这些方法,但在开始使用Mongoose时作为参考很有帮助。

exists() 方法返回 null 或与所提供的查询匹配的第一个文档。以下是根据 author字段匹配文章的示例:

const blog = await Blog.exists({ author: 'Jess Garcia' });console.log(blog);

有关更多信息,请参阅 Model.exists()Mongoose API文档部分。

where() 方法允许您链接和构建复杂的查询。

以下是使用带有普通查询对象的findOne() 以及带有 where() 方法的等效查询来执行查找操作的示例:

const blogFind = await Blog.findOne({ author: 'Jess Garcia' });console.log(blogFind); const blogWhere = await Blog.findOne().where('author').equals('Jess Garcia');console.log(blogWhere);

在此实施中,where()实施从 findOne() 开始, 告诉Mongoose将其视为 findOne()查询。这一点很重要,因为如果您单独使用 where() (Blog.where(...)), Mongoose会将其隐式视为 find() 操作。

通常,where() 方法用于涉及动态查询构建或多个比较器的复杂查询,或者使用此方法提高可读性时。使用 where() 方法与普通对象查询在性能上没有差异。

要在使用 where() 方法时包含投影,请在查询之后链接 select() 方法,如以下示例所示:

const blog = await Blog.findOne() .where('author') .equals('Jess Garcia') .select('title author');console.log(blog);

有关更多信息,请参阅Mongoose API文档的以下部分:

要学习;了解有关将Mongoose与MongoDB结合使用的更多信息,请参阅Mongoose文档。

要寻求支持或为MongoDB Community做出贡献,请参阅MongoDB开发者社区页面。