Overview
Mongoose是一个适用于MongoDB的对象数据建模 (ODM) 库。您可以使用Mongoose来帮助进行数据建模、模式实施、模型验证和一般数据操作。由于MongoDB具有灵活数据模型,允许您将来更改和更新数据库,因此不存在严格的模式。但是, Mongoose从一开始就强制执行半刚性模式,因此您必须定义模式和模型。
Schemas
模式定义集合文档的结构。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
集合。
Tutorial
在本教程中,您将执行以下操作:
设置环境以使用Mongoose
连接至 MongoDB
创建Mongoose模式和模型
插入、更新、查找和删除数据
项目文档字段
验证数据
使用多个模式和中间件
设置您的环境
本教程使用nodemon在本地运行代码。在终端中运行以下命令以初始化项目并安装必要的依赖项:
mkdir mongodb-mongoose cd mongodb-mongoose npm init -y npm i mongoose npm i -D nodemon
在首选代码编辑器中打开项目。本教程使用 ES 模块而不是 CommonJS。必须添加 module
类型才能使用 ES 模块。Gopackage.json
文件并添加以下代码:
... "scripts": { "dev": "nodemon index.js" }, "type": "module", ...
通过运行以下命令,使用 nodemon
启动应用程序:
npm run dev
注意
使用 nodemon 时,每次保存文件时都会运行代码。
连接至 MongoDB
在项目的根级别中,创建一个名为 index.js
的文件,并将以下代码添加到文件顶部:
import mongoose from 'mongoose'; mongoose.connect("<connection string>")
将 <connection string>
占位符替换为MongoDB Atlas连接字符串。 有关如何查找连接字符串的更多信息,请参阅Atlas文档中的连接到集群教程。
创建模式和模型
在开始在MongoDB中添加和更新数据之前,您必须创建模式和模型。
使用Mongoose ,您可以为所需的每个模式创建一个模式模型文件。首先,创建一个名为 model
的文件夹来放置所有模式文件,然后创建您的第一个模式文件并将其命名为 Blog.js
。打开此文件并添加以下代码:
import mongoose from 'mongoose'; const { Schema, model } = mongoose; const blogSchema = 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', blogSchema); export default Blog;
此模式启用 timestamps
选项,将 mongoose 管理的 createdAt
和 updatedAt
字段添加到自动更新的模式中。有关更多信息,请参阅Mongoose文档中的时间戳页面。
执行 CRUD 操作
您现在已经设立了第一个模型和模式,可以开始向数据库插入数据。以下部分向您展示如何使用Mongoose执行增删改查操作。
Insert Data
Goindex.js
并将以下导入声明添加到文件顶部:
import Blog from './model/Blog.js';
在包含连接字符串的行下方添加以下代码:
// Creates a new blog post and inserts it into database const 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 article article.title = "The Most Awesomest Post!!"; await article.save(); console.log('Updated Article:', article);
Updated Article: { title: 'The Most Awesomest Post!!', slug: 'awesome-post', published: true, content: 'This is the best post ever', tags: [ 'featured', 'announcement' ], _id: new ObjectId('...'), comments: [], __v: 0 }
查找数据
要查找特定文档,可以使用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);
Found Article by ID: { _id: new ObjectId('68261c9dae39e390341c367c'), title: 'The Most Awesomest Post!!', slug: 'awesome-post', published: true, content: 'This is the best post ever', tags: [ 'featured', 'announcement' ], comments: [], __v: 0 }
注意
默认下, 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);
Projected Article: { _id: new ObjectId('...'), title: 'The Most Awesomest Post!!', slug: 'awesome-post', content: 'This is the best post ever' }
删除数据
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);
Deleted One Blog: { acknowledged: true, deletedCount: 1 }
要删除多篇文章,可以添加以下代码:
// Deletes all articles with the slug "awesome-post". const blogMany = await Blog.deleteMany({ slug: "awesome-post" }); console.log('Deleted Many Blogs:', blogMany);
Deleted Many Blogs: { acknowledged: true, deletedCount: 4 }
验证数据
在前面步骤中插入的项目不包含 author
、dates
或 comments
字段,即使这些字段包含在模式中。这是因为,尽管您定义了文档的结构,但尚未定义哪些字段是必填字段。任何未定义为必填字段都可以省略。
在Mongoose中,当您对字段进行验证时,必须传递一个对象作为其值。
注意
验证器会自动对 create()
和 save()
方法运行。您可以通过将 runValidators
选项设置为 true
来指定它们在更新方法(例如 update()
和 updateOne()
)上运行。有关验证器的更多信息,请参阅Mongoose文档中的验证页面。
您可以在Mongoose中使用多种验证方法。示例,您可以在任何您想要要求的字段上将 required
设立为 true。您还可以验证类型和格式。在以下代码中,slug
字段被定义为 minLength
为 4
的 string
。这意味着提供少于 4 个字符的 slug
将得到 ValidationError
。
要添加数据验证并定义这些要求,更新Blog.js
中的模式,如以下示例所示:
const blogSchema = 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 database const 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文档中的验证页面。
引入多种模式
接下来,您可以通过创建另一个模式并将其嵌套在博客模式中来增加 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, }, ...
您可以通过更改 blogSchema
定义,为 comment.user
字段重复使用相同的 User
模型:
... // Inside Schema block: comments: [{ user: { type: SchemaTypes.ObjectId, ref: 'User', required: true, }, ...
要在应用程序中使用新用户模型,Goindex.js
文件。 将以下代码添加到文件顶部以导入用户模型:
import User from './model/User.js';
由于您在博客模式中添加了字段验证,因此之前用于插入、更新和删除博客以及指定要项目的字段的代码将无法通过验证,应用程序将出错。
通过添加以下 create()
调用来创建新用户:
// Create a new user const user = await User.create({ name: 'Jess Garica', email: 'jgarcia@email.com', });
使用以下代码更新现有的 create()
调用,以创建一篇将新用户作为作者的新文章,如以下代码所示:
// Creates a new blog post that references the user as the author const 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);
Article with Author: { title: 'Awesome Post!', slug: 'awesome-post', published: false, author: new ObjectId('...'), content: 'This is the best post ever', tags: [ 'featured', 'announcement' ], _id: new ObjectId('...'), createdAt: 2025-05-15T18:05:23.780Z, comments: [], __v: 0 }
前面的代码在MongoDB 数据库中添加一个带有 blogs
集合的 users
集合。此代码添加必填的 author
字段并将其值设置为 user._id
。
要将文章的 name
和 email
字段的值添加到 author
字段,您可以将Mongoose populate()
方法附加到博客查询中。populate()
方法将对 user._id
引用的用户文档执行第二次查询。
将以下代码添加到 index.js
以填充此数据:
// Populates the author field with user data const articlePopulate = await Blog.findOne({ title: "Awesome Post!" }).populate("author"); console.log('Article Populated:', articlePopulate);
Article Populated: { _id: new ObjectId('...'), title: 'Awesome Post!', slug: 'awesome-post', published: false, author: { _id: new ObjectId('...'), name: 'Jess Garica', email: 'jgarcia@email.com', __v: 0 }, content: 'This is the best post ever', tags: [ 'featured', 'announcement' ], createdAt: 2025-05-15T18:04:28.590Z, comments: [], __v: 0 }
有关详细信息,请参阅 Document.populate() Mongoose API文档页面。
添加中间件
在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 Garica', email: 'jgarciaemail.com', });
Error: Invalid email format
如果更正电子邮件解决,则可以看到未引发错误。
除了 pre()
中间件函数外, Mongoose还提供 post()
中间件函数。有关中间件的更多信息,请参阅Mongoose文档中的中间件页面。
后续步骤
您现在有一个示例项目,它使用Mongoose对MongoDB集合执行增删改查操作。 在这里,您可以选择使用更复杂的查询或文档验证来构建此项目。以下部分包括您可能会在应用程序中使用的Mongoose功能的一些示例。
Mongoose自定义 Setter
自定义 setter 会在保存数据时修改数据,并且可以类似于验证器来实现。Mongoose提供以下自定义 setter:
lowercase
uppercase
trim
以下示例将 blog.slug
字段中的字符小写:
const blogSchema = new Schema({ ... slug: { type: String, required: true, minLength: 4, lowercase: true, }, ... });
有关更多信息,请参阅 Mongoose API 文档中的 SchemaStringOptions 页面。
辅助方法
Mongoose包含多个从常规MongoDB方法抽象而来的辅助方法。在本节中,您可以找到其中一些方法的示例。本教程中并未专门使用这些方法,但在开始使用Mongoose时作为参考很有帮助。
exists()
exists()
方法返回 null
或与所提供的查询匹配的第一个文档。以下是根据 author
字段匹配文章的示例:
const blog = await Blog.exists({ author: 'Jess Garcia' }); console.log(blog);
有关更多信息,请参阅 Model.exists()Mongoose API文档部分。
where()
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开发者社区页面。