Mongoose是一个JavaScript框架,通常在带有MongoDB数据库的Node.js应用程序中使用。 在本文中,我将向您介绍Mongoose和MongoDB,更重要的是,这些技术适合您的应用程序。
什么是MongoDB?
让我们从MongoDB开始。 MongoDB是一个将您的数据存储为文档的数据库。 最常见的是,这些文档类似于JSON的结构:
{
firstName: "Jamie",
lastName: "Munro"
}
然后将文档放置在集合中。 作为示例,以上文档示例定义了一个user
对象。 这样,该user
对象通常将成为名为users
的集合的一部分。
MongoDB的关键因素之一是它在结构上的灵活性。 即使在第一个示例中, user
对象包含firstName
和lastName
属性,在属于users
集合的每个user
文档中也不需要这些属性。 这就是使MongoDB与SQL数据库(例如MySQL或Microsoft SQL Server)有很大不同的原因,该SQL数据库要求对其存储的每个对象进行严格定义的数据库架构。
创建可作为文档存储在数据库中的动态对象的功能是Mongoose发挥作用的地方。
什么是猫鼬?
猫鼬是一个对象文档映射器(ODM)。 这意味着Mongoose允许您使用映射到MongoDB文档的强类型模式定义对象。
猫鼬在创建和使用模式方面提供了大量功能。 Mongoose当前包含八个SchemaType,将属性持久化到MongoDB时会将其保存。 他们是:
- 串
- 数
- 日期
- 缓冲
- 布尔型
- 混合的
- 对象编号
- 数组
每种数据类型都允许您指定:
- 默认值
- 自定义验证功能
- 表示必填字段
- 一个get函数,使您可以在数据作为对象返回之前进行操作
- 设置函数,可让您在将数据保存到数据库之前进行操作
- 创建索引以允许更快地获取数据
除了这些常用选项之外,某些数据类型还允许您进一步自定义如何存储数据以及如何从数据库中检索数据。 例如, String
数据类型还允许您指定以下附加选项:
- 将其转换为小写
- 将其转换为大写
- 在保存之前修剪数据
- 一个正则表达式,可以限制在验证过程中允许保存的数据
- 一个可以定义有效字符串列表的枚举
Number
和Date
属性都支持指定该字段允许的最小值和最大值。
您应该非常熟悉这八种允许的数据类型。 但是,可能会跳出一些异常,例如Buffer
, Mixed
, ObjectId
和Array
。
Buffer
数据类型允许您保存二进制数据。 二进制数据的常见示例是图像或编码文件,例如PDF文档。
Mixed
数据类型将属性转换为“一切”字段。 由于没有定义的结构,因此该字段类似于可以使用MongoDB的开发人员数量。 请谨慎使用此数据类型,因为它会丢失Mongoose提供的许多出色功能,例如数据验证和检测实体更改以在保存时自动知道更新属性。
ObjectId
数据类型通常指定到数据库中另一个文档的链接。 例如,如果您有书籍和作者的集合,则书籍文档可能包含一个ObjectId
属性,该属性引用文档的特定作者。
Array
数据类型允许您存储类似JavaScript的数组。 使用Array数据类型,您可以对它们执行常见JavaScript数组操作,例如推,弹出,移位,切片等。
快速回顾
在继续并生成一些代码之前,我只想回顾一下我们刚刚学到的东西。 MongoDB是一个数据库,允许您存储具有动态结构的文档。 这些文档保存在集合中。
Mongoose是一个JavaScript库,允许您使用强类型数据定义架构。 定义架构后,Mongoose允许您基于特定架构创建模型。 然后,通过模型的架构定义将Mongoose模型映射到MongoDB文档。
定义了模式和模型后,Mongoose将包含许多不同的函数,这些函数使您可以使用常见的MongoDB函数来验证,保存,删除和查询数据。 我将在后面的具体代码示例中对此进行更多讨论。
安装MongoDB
在开始创建Mongoose模式和模型之前,必须先安装和配置MongoDB。 我建议访问MongoDB的下载页面 。 有几种不同的选项可供安装。 我已链接到社区服务器。 这使您可以安装特定于您的操作系统的版本。 MongoDB还提供企业服务器和云支持安装。 由于可以编写有关安装,调试和监视MongoDB的全部书籍,因此我将继续使用Community Server。
为您选择的操作系统下载并安装MongoDB之后,您将需要启动数据库。 建议不要访问MongoDB,而是建议访问MongoDB有关如何安装MongoDB Community Edition的文档。
在您配置MongoDB时,我将在这里等待。 准备就绪后,我们可以继续设置Mongoose以连接到新安装的MongoDB数据库。
设置猫鼬
猫鼬是一个JavaScript框架,我将在Node.js应用程序中使用它。 如果已经安装了Node.js,则可以继续执行下一步。 如果您尚未安装Node.js,建议您首先访问Node.js下载页面,然后为您的操作系统选择安装程序。
设置好Node.js并准备就绪后,我将创建一个新应用程序,然后安装Mongoose NPM软件包。
通过将命令提示符设置为希望将应用程序安装到的位置,可以运行以下命令:
mkdir mongoose_basics
cd mongoose_basics
npm init
对于应用程序的初始化,我将所有内容保留为其默认值。 现在,我将如下安装猫鼬软件包:
npm install mongoose --save
配置了所有先决条件后,让我们连接到MongoDB数据库。 我将以下代码放置在index.js文件中,因为我选择将其作为应用程序的起点:
var mongoose = require('mongoose');
mongoose.connect('mongodb://localhost/mongoose_basics');
代码的第一行包括mongoose
库。 接下来,我使用connect
函数打开到名为mongoose_basics
的数据库的connect
。
connect
函数接受其他两个可选参数。 第二个参数是选项的对象,如果需要,您可以在其中定义用户名和密码之类的内容。 第三个参数,如果没有选项,也可以是第二个参数,它是尝试连接后的回调函数。 回调函数可以通过以下两种方式之一使用:
mongoose.connect(uri, options, function(error) {
// Check error in initial connection. There is no 2nd param to the callback.
});
// Or using promises
mongoose.connect(uri, options).then(
() => { /** ready to use. The `mongoose.connect()` promise resolves to undefined. */ },
err => { /** handle initial connection error */ }
);
为了避免潜在地介绍JavaScript Promises ,我将使用第一种方法。 以下是更新的index.js文件:
var mongoose = require('mongoose');
mongoose.connect('mongodb://localhost/mongoose_basics', function (err) {
if (err) throw err;
console.log('Successfully connected');
});
如果连接到数据库时发生错误,则会引发异常,并停止所有进一步的处理。 当没有错误发生时,我已将成功消息记录到控制台。
现在已经建立了Mongoose并将其连接到名为mongoose_basics
的数据库。 我的MongoDB连接未使用用户名,密码或自定义端口。 如果您需要在连接期间设置这些选项或任何其他选项,建议您阅读有关连接的Mongoose文档 。 该文档提供了许多可用选项的详细说明,以及如何创建多个连接,连接池,副本等。
成功建立连接后,让我们继续定义猫鼬模式。
定义猫鼬模式
在介绍中,我展示了一个包含两个属性的user
对象: firstName
和lastName
。 在以下示例中,我将该文档转换为Mongoose模式:
var userSchema = mongoose.Schema({
firstName: String,
lastName: String
});
这是一个非常基本的架构,仅包含两个属性,没有与之关联的属性。 让我们将第一和最后一个名称的属性是一个的子对象在本例中展开name
属性。 name
属性将包含名字和姓氏。 我还将添加一个created
的类型为Date
属性。
var userSchema = mongoose.Schema({
name: {
firstName: String,
lastName: String
},
created: Date
});
如您所见,Mongoose允许我创建非常灵活的架构,并具有许多不同的组织数据方式的组合。
在下一个示例中,我将创建两个新的模式,这些模式将演示如何创建与另一个模式的关系: author
和book
。 book
模式将包含对author
模式的引用。
var authorSchema = mongoose.Schema({
_id: mongoose.Schema.Types.ObjectId,
name: {
firstName: String,
lastName: String
},
biography: String,
twitter: String,
facebook: String,
linkedin: String,
profilePicture: Buffer,
created: {
type: Date,
default: Date.now
}
});
上面的author
架构扩展了我在上一个示例中创建的user
架构的概念。 要将Author和Book链接在一起, author
架构的第一个属性是_id
属性,它是ObjectId
架构类型。 _id
是在Mongoose和MongoDB中创建主键的常用语法。 然后,像user
模式一样,我定义了一个name
属性,其中包含作者的名字和姓氏。
在user
模式上展开时, author
包含其他几种String
模式类型。 我还添加了可以容纳作者个人资料图片的Buffer
模式类型。 最终属性保存作者的创建日期; 但是,您可能会注意到它的创建方式略有不同,因为它已将默认值定义为“ now”。 当作者保留在数据库中时,此属性将设置为当前日期/时间。
为了完成模式示例,我们使用ObjectId
模式类型创建一个包含对作者的引用的book
模式:
var bookSchema = mongoose.Schema({
_id: mongoose.Schema.Types.ObjectId,
title: String,
summary: String,
isbn: String,
thumbnail: Buffer,
author: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Author'
},
ratings: [
{
summary: String,
detail: String,
numberOfStars: Number,
created: {
type: Date,
default: Date.now
}
}
],
created: {
type: Date,
default: Date.now
}
});
book
模式包含String
类型的几个属性。 如上所述,它包含对author
架构的引用。 为了进一步证明了强大的模式定义, book
架构还包含一个Array
的ratings
。 每个评分numberOfStars
summary
, detail
, numberOfStars
和created
日期属性组成。
Mongoose使您可以灵活地创建对其他模式的引用来创建模式,或者如上例中带有ratings
属性的示例一样,它允许您创建可以包含在相关模式(如书本到作者)中的子属性Array
。内联,如上面的示例(将书添加到rating Array
)。
创建和保存猫鼬模型
由于author
和book
模式展示了Mongoose的模式灵活性,因此我将继续使用这些模式并从中派生出Author
和Book
模型。
var Author = mongoose.model('Author', authorSchema);
var Book = mongoose.model('Book', bookSchema);
Mongoose模型在保存时会在MongoDB中创建一个文档,该文档具有由其派生的架构定义的属性。
为了演示如何创建和保存对象,在下一个示例中,我将创建几个对象:一个Author
Model和几个Book
Model。 一旦创建,这些对象将使用Model的save
方法save
到MongoDB中。
var jamieAuthor = new Author {
_id: new mongoose.Types.ObjectId(),
name: {
firstName: 'Jamie',
lastName: 'Munro'
},
biography: 'Jamie is the author of ASP.NET MVC 5 with Bootstrap and Knockout.js.',
twitter: 'https://twitter.com/endyourif',
facebook: 'https://www.facebook.com/End-Your-If-194251957252562/'
};
jamieAuthor.save(function(err) {
if (err) throw err;
console.log('Author successfully saved.');
var mvcBook = new Book {
_id: new mongoose.Types.ObjectId(),
title: 'ASP.NET MVC 5 with Bootstrap and Knockout.js',
author: jamieAuthor._id,
ratings:[{
summary: 'Great read'
}]
};
mvcBook.save(function(err) {
if (err) throw err;
console.log('Book successfully saved.');
});
var knockoutBook = new Book {
_id: new mongoose.Types.ObjectId(),
title: 'Knockout.js: Building Dynamic Client-Side Web Applications',
author: jamieAuthor._id
};
knockoutBook.save(function(err) {
if (err) throw err;
console.log('Book successfully saved.');
});
});
在上面的示例中,我无耻地插入了对我的两本书的引用。 该示例首先创建并保存从Author
Model创建的jamieObject
。 在jamieObject
的save
函数内部,如果发生错误,则应用程序将输出异常。 保存成功后,将在save
功能内部创建并保存两个书本对象。 与jamieObject
相似,如果保存时发生错误,则会输出错误。 否则,在控制台中输出成功消息。
为了创建对Author的引用,book对象都在book
schema的author
属性中引用了author
schema的_id
主键。
保存前验证数据
最终将创建模型的数据由网页上的表单填充的情况非常普遍。 因此,在将模型保存到MongoDB之前,先验证此数据是一个好主意。
在下一个示例中,我更新了以前的作者架构,以添加对以下属性的验证: firstName
, twitter
, facebook
和linkedin
。
var authorSchema = mongoose.Schema({
_id: mongoose.Schema.Types.ObjectId,
name: {
firstName: {
type: String,
required: true
},
lastName: String
},
biography: String,
twitter: {
type: String,
validate: {
validator: function(text) {
return text.indexOf('https://twitter.com/') === 0;
},
message: 'Twitter handle must start with https://twitter.com/'
}
},
facebook: {
type: String,
validate: {
validator: function(text) {
return text.indexOf('https://www.facebook.com/') === 0;
},
message: 'Facebook must start with https://www.facebook.com/'
}
},
linkedin: {
type: String,
validate: {
validator: function(text) {
return text.indexOf('https://www.linkedin.com/') === 0;
},
message: 'LinkedIn must start with https://www.linkedin.com/'
}
},
profilePicture: Buffer,
created: {
type: Date,
default: Date.now
}
});
firstName
属性已使用required
属性进行属性。 现在,当我调用save
函数时,Mongoose将返回一条错误消息,并指出需要firstName
属性。 我选择不设置所需的lastName
属性,以防雪儿或麦当娜成为我数据库中的作者。
twitter
, facebook
和linkedin
属性都应用了非常相似的自定义验证器。 它们每个都确保值以社交网络各自的域名开头。 这些字段不是必需的,因此仅在为该属性提供数据时才应用验证器。
搜索和更新数据
如果没有搜索记录并更新该对象的一个或多个属性的示例,对Mongoose的介绍就不会完整。
猫鼬提供了几种不同的功能来查找特定模型的数据。 这些函数是find
, findOne
和findById
。
find
和findOne
函数都接受一个对象作为输入,允许进行复杂的搜索,而findById
仅接受带有回调函数的单个值(稍后将举一个示例)。 在下一个示例中,我将演示如何查找标题中包含字符串“ mvc”的所有书籍。
Book.find({
title: /mvc/i
}).exec(function(err, books) {
if (err) throw err;
console.log(books);
});
在find
函数中,我正在title
属性上搜索不区分大小写的字符串“ mvc”。 这可以通过使用与JavaScript搜索字符串相同的语法来完成。
find函数调用还链接到其他查询方法,例如where
and
or
limit
, sort
, any
等。
让我们扩展前面的示例,将结果限制在前五本书中,并以创建日期降序排序。 这将返回标题中包含“ mvc”的最新五本书。
Book.find({
title: /mvc/i
}).sort('-created')
.limit(5)
.exec(function(err, books) {
if (err) throw err;
console.log(books);
});
应用后find
功能,因为所有的链接功能被编译成一个单一的查询,而不是执行,直到其他功能的顺序并不重要exec
函数被调用。
正如我前面提到的, findById
的执行方式findById
不同。 它立即执行并接受回调函数,而不是允许使用一系列函数。 在下一个示例中,我通过_id
查询特定作者。
Author.findById('59b31406beefa1082819e72f', function(err, author) {
if (err) throw err;
console.log(author);
});
您的情况下的_id
可能略有不同。 当查找书名中带有“ mvc”的书籍列表时,我从以前的console.log
复制了此_id
。
返回对象后,您可以修改其任何属性以更新它。 进行必要的更改后,就可以调用save
方法,就像创建对象时一样。 在下一个示例中,我将扩展findbyId
示例并更新作者的linkedin
属性。
Author.findById('59b31406beefa1082819e72f', function(err, author) {
if (err) throw err;
author.linkedin = 'https://www.linkedin.com/in/jamie-munro-8064ba1a/';
author.save(function(err) {
if (err) throw err;
console.log('Author updated successfully');
});
});
成功检索作者后,将设置linkedin
属性并调用save
功能。 Mongoose能够检测到linkedin
属性已更改,并且它将仅对已修改的属性发送一条更新语句到MongoDB。 如果保存时发生错误,将引发异常并停止应用程序。 成功后,将成功消息记录到控制台。
Mongoose还提供了两个附加函数,这些函数可以查找对象并使用适当命名的函数一步将其保存: findByIdAndUpdate
和findOneAndUpdate
。 让我们更新前面的示例以使用findByIdAndUpdate
。
Author.findByIdAndUpdate('59b31406beefa1082819e72f',
{ linkedin: 'https://www.linkedin.com/in/jamie-munro-8064ba1a/' },
function(err, author) {
if (err) throw err;
console.log(author);
});
在前面的示例中,要更新的属性作为对象提供给findByIdAndUpdate
函数的第二个参数。 现在,回调函数是第三个参数。 更新成功后,返回的author
对象将包含更新的信息。 这被记录到控制台以查看更新的作者的属性。
最终样本代码
在整个本文中,我提供了一些代码片段,这些代码片段标识了非常具体的操作,例如创建模式,创建模型等。让我们将它们全部放到一个完整的示例中。
首先,我创建了两个附加文件: author.js
和book.js
这些文件包含其各自的架构定义和模型创建。 代码的最后一行使该模型可在index.js
文件中使用。
让我们从author.js文件开始:
var mongoose = require('mongoose');
var authorSchema = mongoose.Schema({
_id: mongoose.Schema.Types.ObjectId,
name: {
firstName: {
type: String,
required: true
},
lastName: String
},
biography: String,
twitter: {
type: String,
validate: {
validator: function(text) {
return text.indexOf('https://twitter.com/') === 0;
},
message: 'Twitter handle must start with https://twitter.com/'
}
},
facebook: {
type: String,
validate: {
validator: function(text) {
return text.indexOf('https://www.facebook.com/') === 0;
},
message: 'Facebook must start with https://www.facebook.com/'
}
},
linkedin: {
type: String,
validate: {
validator: function(text) {
return text.indexOf('https://www.linkedin.com/') === 0;
},
message: 'LinkedIn must start with https://www.linkedin.com/'
}
},
profilePicture: Buffer,
created: {
type: Date,
default: Date.now
}
});
var Author = mongoose.model('Author', authorSchema);
module.exports = Author;
接下来是book.js
文件:
var mongoose = require('mongoose');
var bookSchema = mongoose.Schema({
_id: mongoose.Schema.Types.ObjectId,
title: String,
summary: String,
isbn: String,
thumbnail: Buffer,
author: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Author'
},
ratings: [
{
summary: String,
detail: String,
numberOfStars: Number,
created: {
type: Date,
default: Date.now
}
}
],
created: {
type: Date,
default: Date.now
}
});
var Book = mongoose.model('Book', bookSchema);
module.exports = Book;
最后,更新的index.js
文件:
var mongoose = require('mongoose');
var Author = require('./author');
var Book = require('./book');
mongoose.connect('mongodb://localhost/mongoose_basics', function (err) {
if (err) throw err;
console.log('Successfully connected');
var jamieAuthor = new Author({
_id: new mongoose.Types.ObjectId(),
name: {
firstName: 'Jamie',
lastName: 'Munro'
},
biography: 'Jamie is the author of ASP.NET MVC 5 with Bootstrap and Knockout.js.',
twitter: 'https://twitter.com/endyourif',
facebook: 'https://www.facebook.com/End-Your-If-194251957252562/'
});
jamieAuthor.save(function(err) {
if (err) throw err;
console.log('Author successfully saved.');
var mvcBook = new Book({
_id: new mongoose.Types.ObjectId(),
title: 'ASP.NET MVC 5 with Bootstrap and Knockout.js',
author: jamieAuthor._id,
ratings:[{
summary: 'Great read'
}]
});
mvcBook.save(function(err) {
if (err) throw err;
console.log('Book successfully saved.');
});
var knockoutBook = new Book({
_id: new mongoose.Types.ObjectId(),
title: 'Knockout.js: Building Dynamic Client-Side Web Applications',
author: jamieAuthor._id
});
knockoutBook.save(function(err) {
if (err) throw err;
console.log('Book successfully saved.');
});
});
});
在上面的示例中,所有Mongoose操作都包含在connect
函数中。 在包含mongoose
库之后, author
和book
文件便包含在require
函数中。
随着MongoDB的运行,您现在可以使用以下命令运行完整的Node.js应用程序:
node index.js
将一些数据保存到数据库后,我使用查找功能更新了index.js
文件,如下所示:
var mongoose = require('mongoose');
var Author = require('./author');
var Book = require('./book');
mongoose.connect('mongodb://localhost/mongoose_basics', function (err) {
if (err) throw err;
console.log('Successfully connected');
Book.find({
title: /mvc/i
}).sort('-created')
.limit(5)
.exec(function(err, books) {
if (err) throw err;
console.log(books);
});
Author.findById('59b31406beefa1082819e72f', function(err, author) {
if (err) throw err;
author.linkedin = 'https://www.linkedin.com/in/jamie-munro-8064ba1a/';
author.save(function(err) {
if (err) throw err;
console.log('Author updated successfully');
});
});
Author.findByIdAndUpdate('59b31406beefa1082819e72f', { linkedin: 'https://www.linkedin.com/in/jamie-munro-8064ba1a/' }, function(err, author) {
if (err) throw err;
console.log(author);
});
});
您可以再次使用以下命令运行该应用程序: node index.js
。
摘要
阅读本文之后,您应该能够创建极其灵活的Mongoose架构和模型,应用简单或复杂的验证,创建和更新文档,最后搜索创建的文档。
希望您现在可以轻松使用Mongoose。 如果您想了解更多信息,我建议您阅读《 猫鼬指南》 ,其中深入探讨了诸如人口,中间件,承诺等更高级的主题。
狩猎快乐(可怜的猫鼬动物参考)!
翻译自: https://code.tutsplus.com/articles/an-introduction-to-mongoose-for-mongodb-and-nodejs--cms-29527