Mongoose for MongoDB 简介
Mongoose 是一个用于 MongoDB 和 Node.js 的对象数据建模 (ODM) 库。它管理数据之间的关系,提供模式验证,并用于在代码中的对象和 MongoDB 中这些对象的表示之间进行转换。
[](https://res.cloudinary.com/practicaldev/image/fetch/s--CwcjvN6o--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1. medium.com/max/1024/0%2Ab5piDNW1dqlkJWKe。)
通过 Mongoose 管理的 Node 和 MongoDB 之间的对象映射
MongoDB 是一个无模式的 NoSQL 文档数据库。这意味着您可以在其中存储 JSON 文档,并且这些文档的结构可能会有所不同,因为它不像 SQL 数据库那样强制执行。这是使用 NoSQL 的优势之一,因为它可以加快应用程序开发并降低部署的复杂性。
下面是数据如何存储在 Mongo 与 SQL 数据库中的示例:
[
](https://res.cloudinary.com/practicaldev/image/fetch/s--KqdApFnS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https: //thepracticaldev.s3.amazonaws.com/i/xwccz9hq6adxiplvb32b.png)
NoSQL 文档与 SQL 中的关系表
术语
收藏品
Mongo 中的“集合”相当于关系数据库中的表。它们可以保存多个 JSON 文档。
文件
“文档”相当于 SQL 中的记录或数据行。虽然 SQL 行可以引用其他表中的数据,但 Mongo 文档通常将其组合在一个文档中。
字段
“字段”或属性类似于 SQL 表中的列。
架构
虽然 Mongo 是无模式的,但 SQL 通过表定义来定义模式。 Mongoose“模式”是通过应用层强制执行的文档数据结构(或文档的形状)。
型号
“模型”是高阶构造函数,它采用模式并创建与关系数据库中的记录等效的文档实例。
入门
Mongo安装
在开始之前,让我们设置 Mongo。您可以选择以下选项之一(我们在本文中使用选项 #1):
- 从MongoDB 网站下载适合您操作系统的 MongoDB 版本,然后按照他们的安装说明进行操作
2.创建免费沙盒数据库在mLab上订阅
3.使用 Docker 安装 Mongo如果您更喜欢使用 docker
让我们通过实现一个表示简化地址簿数据的模型来浏览 Mongoose 的一些基础知识。
我正在使用 Visual Studio Code、Node 8.9 和 NPM 5.6。启动您最喜欢的 IDE,创建一个空白项目,让我们开始吧!我们将在 Node 中使用有限的 ES6 语法,因此我们不会配置 Babel。
NPM 安装
让我们进入项目文件夹并初始化我们的项目
npm init -y
进入全屏模式 退出全屏模式
让我们使用以下命令安装 Mongoose 和验证库:
npm install mongoose validator
进入全屏模式 退出全屏模式
上面的安装命令将安装最新版本的库。本文中的 Mongoose 语法特定于 Mongoose v5 及更高版本。
数据库连接
在项目根目录下创建一个文件**./src/database.js**。
接下来,我们将添加一个带有连接数据库的方法的简单类。
您的连接字符串会因您的安装而异。
let mongoose = require('mongoose');
const server = '127.0.0.1:27017'; // REPLACE WITH YOUR DB SERVER
const database = 'fcc-Mail'; // REPLACE WITH YOUR DB NAME
class Database {
constructor() {
this._connect()
}
_connect() {
mongoose.connect(`mongodb://${server}/${database}`)
.then(() => {
console.log('Database connection successful')
})
.catch(err => {
console.error('Database connection error')
})
}
}
module.exports = new Database()
进入全屏模式 退出全屏模式
上面的 require('mongoose') 调用返回一个 Singleton 对象。这意味着你第一次调用 require('mongoose') 时,它正在创建一个 Mongoose 类的实例并返回它。在随后的调用中,它将返回与第一次创建并返回给您的实例相同的实例,因为模块导入/导出在 ES6 中是如何工作的。
[](https://res.cloudinary.com/practicaldev/image/fetch/s--lC1O1wA9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1. medium.com/max/1024/0%2ARvVsD_byUakUzuCj。)
模块导入/需要工作流程
同样,我们通过在 module.exports 语句中返回类的一个实例,将我们的 Database 类变成了一个单例,因为我们只需要一个到数据库的连接。
ES6 使我们可以很容易地创建单例(单实例)模式,因为模块加载器通过缓存先前导入文件的响应来工作。
Mongoose 模式与模型
Mongoose 模型是 Mongoose 模式的包装器。 Mongoose 模式定义了文档的结构、默认值、验证器等,而 Mongoose 模型为数据库提供了一个接口,用于创建、查询、更新、删除记录等。
创建 Mongoose 模型主要包括三个部分:
1\。参考猫鼬
let mongoose = require('mongoose')
进入全屏模式 退出全屏模式
此引用将与我们连接到数据库时返回的引用相同,这意味着模式和模型定义不需要显式连接到数据库。
2\。定义架构
模式通过一个对象定义文档属性,其中键名对应于集合中的属性名。
let emailSchema = new mongoose.Schema({
email: String
})
进入全屏模式 退出全屏模式
在这里,我们定义了一个名为 email 的属性,其模式类型为 String,它映射到一个内部验证器,该验证器将在模型保存到数据库时触发。如果值的数据类型不是字符串类型,它将失败。
允许以下模式类型:
-
数组
-
布尔值
-
缓冲器
-
日期
-
混合(通用/灵活数据类型)
-
号码
-
对象标识
-
字符串
Mixed 和 ObjectId 在 require('mongoose').Schema.Types 下定义。
3\。导出模型
我们需要在 Mongoose 实例上调用模型构造函数,并将集合的名称和对模式定义的引用传递给它。
module.exports = mongoose.model('Email', emailSchema)
进入全屏模式 退出全屏模式
让我们将上面的代码合并到 ./src/models/email.js 中来定义一个基本的电子邮件模型的内容:
let mongoose = require('mongoose')
let emailSchema = new mongoose.Schema({
email: String
})
module.exports = mongoose.model('Email', emailSchema)
进入全屏模式 退出全屏模式
模式定义应该很简单,但其复杂性通常基于应用程序需求。模式可以重复使用,它们也可以包含多个子模式。在上面的示例中,电子邮件属性的值是一个简单的值类型。但是,它也可以是带有附加属性的对象类型。
我们可以创建上面定义的模型的一个实例,并使用以下语法填充它:
let EmailModel = require('./email')
let msg = new EmailModel({
email: 'ada.lovelace@gmail.com'
})
进入全屏模式 退出全屏模式
让我们增强电子邮件模式,使电子邮件属性成为唯一的必填字段,并在保存之前将值转换为小写。我们还可以添加一个验证函数,以确保该值是有效的电子邮件地址。我们将引用和使用之前安装的验证器库。
let mongoose = require('mongoose')
let validator = require('validator')
let emailSchema = new mongoose.Schema({
email: {
type: String,
required: true,
unique: true,
lowercase: true,
validate: (value) => {
return validator.isEmail(value)
}
}
})
module.exports = mongoose.model('Email', emailSchema)
进入全屏模式 退出全屏模式
基本操作
Mongoose 有一个灵活的 API 并提供了许多方法来完成一项任务。我们不会关注变体,因为这超出了本文的范围,但请记住,大多数操作可以通过不止一种方式在语法上或通过应用程序架构完成。
创建记录
让我们创建一个电子邮件模型的实例并将其保存到数据库中:
let EmailModel = require('./email')
let msg = new EmailModel({
email: 'ADA.LOVELACE@GMAIL.COM'
})
msg.save()
.then(doc => {
console.log(doc)
})
.catch(err => {
console.error(err)
})
进入全屏模式 退出全屏模式
结果是成功保存后返回的文档:
{
_id: 5a78fe3e2f44ba8f85a2409a,
email: 'ada.lovelace@gmail.com',
__v: 0
}
进入全屏模式 退出全屏模式
返回以下字段(内部字段以下划线为前缀):
-
_id 字段是 Mongo 自动生成的,是集合的主键。它的值是文档的唯一标识符。
-
email 字段的值被返回。请注意,它是小写的,因为我们在模式中指定了 lowercase:true 属性。
-
__v 是 Mongoose 首次创建时在每个文档上设置的 versionKey 属性。它的值包含文档的内部修订。
如果你尝试重复上面的保存操作,你会得到一个错误,因为我们已经指定了 email 字段应该是唯一的。
获取记录
让我们尝试检索我们之前保存到数据库中的记录。模型类公开了几个静态和实例方法来对数据库执行操作。我们现在将尝试使用 find 方法查找我们之前创建的记录,并将电子邮件作为搜索词传递。
EmailModel
.find({
email: 'ada.lovelace@gmail.com' // search query
})
.then(doc => {
console.log(doc)
})
.catch(err => {
console.error(err)
})
进入全屏模式 退出全屏模式
返回的文档将类似于我们创建记录时显示的内容:
{
_id: 5a78fe3e2f44ba8f85a2409a,
email: 'ada.lovelace@gmail.com',
__v: 0
}
进入全屏模式 退出全屏模式
更新记录
让我们通过更改电子邮件地址并向其中添加另一个字段来修改上面的记录,所有这些都在一个操作中完成。出于性能原因,Mongoose 不会返回更新后的文档,因此我们需要传递一个额外的参数来请求它:
EmailModel
.findOneAndUpdate(
{
email: 'ada.lovelace@gmail.com' // search query
},
{
email: 'theoutlander@live.com' // field:values to update
},
{
new: true, // return updated doc
runValidators: true // validate before update
})
.then(doc => {
console.log(doc)
})
.catch(err => {
console.error(err)
})
进入全屏模式 退出全屏模式
返回的文档将包含更新后的电子邮件:
{
_id: 5a78fe3e2f44ba8f85a2409a,
email: 'theoutlander@live.com',
__v: 0
}
进入全屏模式 退出全屏模式
删除记录
我们将使用 findOneAndRemove 调用来删除一条记录。它返回被删除的原始文档:
EmailModel
.findOneAndRemove({
email: 'theoutlander@live.com'
})
.then(response => {
console.log(response)
})
.catch(err => {
console.error(err)
})
进入全屏模式 退出全屏模式
帮手
我们已经查看了上面称为 CRUD(创建、读取、更新、删除)操作的一些基本功能,但 Mongoose 还提供了配置多种类型的辅助方法和属性的能力。这些可用于进一步简化数据处理。
让我们在 ./src/models/user.js 中创建一个用户模式,其中包含字段 firstName 和 lastName:
let mongoose = require('mongoose')
let userSchema = new mongoose.Schema({
firstName: String,
lastName: String
})
module.exports = mongoose.model('User', userSchema)
进入全屏模式 退出全屏模式
虚拟财产
虚拟属性不会持久化到数据库中。我们可以将它添加到我们的模式中,作为获取和设置值的助手。
让我们创建一个名为 fullName 的虚拟属性,该属性可用于设置 firstName 和 lastName 的值,并在读取时将它们作为组合值检索:
userSchema.virtual('fullName').get(function() {
return this.firstName + ' ' + this.lastName
})
userSchema.virtual('fullName').set(function(name) {
let str = name.split(' ')
this.firstName = str[0]
this.lastName = str[1]
})
进入全屏模式 退出全屏模式
get 和 set 的回调必须使用 function 关键字,因为我们需要通过 this 关键字访问模型。使用胖箭头函数将改变 this 所指的内容。
现在,我们可以通过为 fullName 赋值来设置 firstName 和 lastName:
let model = new UserModel()
model.fullName = 'Thomas Anderson'
console.log(model.toJSON()) // Output model fields as JSON
console.log()
console.log(model.fullName) // Output the full name
进入全屏模式 退出全屏模式
上面的代码将输出以下内容:
{ _id: 5a7a4248550ebb9fafd898cf,
firstName: 'Thomas',
lastName: 'Anderson' }
Thomas Anderson
进入全屏模式 退出全屏模式
实例方法
我们可以在模式上创建自定义辅助方法并通过模型实例访问它们。这些方法可以访问模型对象,并且可以非常有创意地使用它们。例如,我们可以创建一个方法来查找与当前实例具有相同名字的所有人。
在这个例子中,让我们创建一个函数来返回当前用户的姓名首字母。让我们在模式中添加一个名为 getInitials 的自定义辅助方法:
userSchema.methods.getInitials = function() {
return this.firstName[0] + this.lastName[0]
}
进入全屏模式 退出全屏模式
可以通过模型实例访问此方法:
let model = new UserModel({
firstName: 'Thomas',
lastName: 'Anderson'
})
let initials = model.getInitials()
console.log(initials) // This will output: TA
进入全屏模式 退出全屏模式
静态方法
与实例方法类似,我们可以在模式上创建静态方法。让我们创建一个方法来检索数据库中的所有用户:
userSchema.statics.getUsers = function() {
return new Promise((resolve, reject) => {
this.find((err, docs) => {
if(err) {
console.error(err)
return reject(err)
}
resolve(docs)
})
})
}
进入全屏模式 退出全屏模式
在 Model 类上调用 getUsers 将返回数据库中的所有用户:
UserModel.getUsers()
.then(docs => {
console.log(docs)
})
.catch(err => {
console.error(err)
})
进入全屏模式 退出全屏模式
添加实例和静态方法是实现集合和记录上的数据库交互接口的好方法。
中间件
中间件是在管道的特定阶段运行的函数。 Mongoose 支持以下操作的中间件:
-
骨料
-
文件
-
型号
-
查询
例如,模型具有带有两个参数的 pre 和 post 函数:
-
事件类型(“init”、“validate”、“save”、“remove”)
-
使用 this 引用模型实例执行的回调
[](https://res.cloudinary.com/practicaldev/image/fetch/s--vZRxNkJ7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1。 medium.com/max/1024/0%2AiZwmyy25FSxuxXlH。)
中间件示例(也称为 pre 和 post 挂钩)
让我们通过将两个名为 createdAt 和 updatedAt 的字段添加到我们的模式来尝试一个示例:
let mongoose = require('mongoose')
let userSchema = new mongoose.Schema({
firstName: String,
lastName: String,
createdAt: Date,
updatedAt: Date
})
module.exports = mongoose.model('User', userSchema)
进入全屏模式 退出全屏模式
当调用 model.save() 时,会触发 pre('save', ...) 和 post('save', ...) 事件。对于第二个参数,您可以传递一个在事件触发时调用的函数。这些函数将参数传递给中间件链中的下一个函数。
让我们添加一个预保存钩子并为 createdAt 和 updatedAt 设置值:
userSchema.pre('save', function (next) {
let now = Date.now()
this.updatedAt = now
// Set a value for createdAt only if it is null
if (!this.createdAt) {
this.createdAt = now
}
// Call the next function in the pre-save chain
next()
})
进入全屏模式 退出全屏模式
让我们创建并保存我们的模型:
let UserModel = require('./user')
let model = new UserModel({
fullName: 'Thomas Anderson'
}
msg.save()
.then(doc => {
console.log(doc)
})
.catch(err => {
console.error(err)
})
进入全屏模式 退出全屏模式
打印创建的记录时,您应该会看到 createdAt 和 updatedAt 的值:
{ _id: 5a7bbbeebc3b49cb919da675,
firstName: 'Thomas',
lastName: 'Anderson',
updatedAt: 2018-02-08T02:54:38.888Z,
createdAt: 2018-02-08T02:54:38.888Z,
__v: 0 }
进入全屏模式 退出全屏模式
插件
假设我们想要跟踪数据库中每个集合的记录创建时间和最后更新时间。我们可以创建一个插件并将其应用于每个模式,而不是重复上述过程。
让我们创建一个文件 ./src/model/plugins/timestamp.js 并将上述功能复制为可重用模块:
module.exports = function timestamp(schema) {
// Add the two fields to the schema
schema.add({
createdAt: Date,
updatedAt: Date
})
// Create a pre-save hook
schema.pre('save', function (next) {
let now = Date.now()
this.updatedAt = now
// Set a value for createdAt only if it is null
if (!this.createdAt) {
this.createdAt = now
}
// Call the next function in the pre-save chain
next()
})
}
进入全屏模式 退出全屏模式
要使用这个插件,我们只需将它传递给应该被赋予这个功能的模式:
let timestampPlugin = require('./plugins/timestamp')
emailSchema.plugin(timestampPlugin)
userSchema.plugin(timestampPlugin)
进入全屏模式 退出全屏模式
查询楼
Mongoose 有一个非常丰富的 API,可以处理 MongoDB 支持的许多复杂操作。考虑一个我们可以增量构建查询组件的查询。
在本例中,我们将:
1.查找所有用户
-
跳过前 100 条记录
-
将结果限制为 10 条记录
-
按 firstName 字段对结果进行排序
5.选择名字
- 执行该查询
UserModel.find() // find all users
.skip(100) // skip the first 100 items
.limit(10) // limit to 10 items
.sort({firstName: 1} // sort ascending by firstName
.select({firstName: true} // select firstName only
.exec() // execute the query
.then(docs => {
console.log(docs)
})
.catch(err => {
console.error(err)
})
进入全屏模式 退出全屏模式
结束
我们几乎没有触及到探索 Mongoose 的一些功能的皮毛。它是一个丰富的库,充满了有用且强大的功能,使在应用程序层中使用数据模型成为一种乐趣。
虽然您可以使用 Mongo Driver 直接与 Mongo 交互,但 Mongoose 将通过允许您对数据之间的关系进行建模并轻松验证它们来简化交互。
有趣的事实:Mongoose由Valeri Karpov创建,他是一位非常有才华的工程师!他创造了术语The MEAN Stack。
单击此处查看我即将开设的课程:Mongoose 开发人员完整指南
如果这篇文章有帮助,❤️ 它和在 Twitter 上关注我。
[

](/外乡人)[
如何构建自己的 React 样板
尼克卡尼克 1 月 22 日 18 日 13 m 里德
#javascript #webdev #node #react
](/theoutlander/build-your-own-react-boilerplate-4b8l)
更多推荐
所有评论(0)