前言
Node.js 是一门非常适合前端工程师入门后学习的后端语言,它基于 JavaScript,充分利用了前端工程师的技能,同时其强大的异步 I/O 模型也使得它在处理网络请求这一方面表现优异。而 Mongoose 则是 Node.js 中使用最广泛的 MongoDB 官方驱动之一,它可以提供方便、灵活且高效的数据操作能力,几乎是每个 Node.js 开发者都应该熟练掌握的技术之一。
本文将为读者介绍 Mongoose 的相关概念、用法和实战案例,并探讨如何将 Mongoose 融入到实际的项目中,并为读者提供实用的指导和建议。
什么是 Mongoose
Mongoose 是一个 Node.js 的对象模型工具,可以帮助我们更方便地操作 MongoDB 数据库。它提供了许多实用的方法和功能,例如:
- 模式(Schema)定义:可以定义不同 MongoDB 文档的结构,包括字段、数据类型、默认值和验证规则等;
- 模型(Model)创建:可通过指定模式创建 MongoDB 文档的实例,进行增删改查等操作;
- 钩子(Hooks)定义:可以在文档保存、更新、删除等触发点执行特定的操作;
- 中间件(Middleware)定义:可以在文档操作前后执行用户特定的函数;
- 查询定义:可使用 Mongoose 提供的查询操作符,以更灵活的方式查询文档;
- Populates:可通过 populate() 方法将两个集合之间的关联查询出来并将其嵌入到其他集合中。
下面我们将逐个了解这些概念,及其在实际开发中的应用。
模式(Schema)定义
为了更好地利用 Mongoose,我们首先需要定义文档的结构,通过 Schema 的定义来规范文档的格式。这里我们以一个用户管理系统为例,假设我们要定义一个用户的 Schema,包含用户名、密码、邮箱、手机号等属性,代码如下:
const userSchema = new mongoose.Schema({
username: { type: String, required: true },
password: { type: String, required: true },
email: { type: String, required: true },
mobile: { type: String, required: false }
});这段代码定义了一个名为 userSchema 的 Schema 对象。它的定义包含了四个字段,分别是用户名(必须)、密码(必须)、邮箱(必须)和手机号码(非必须)。
我们可以看到,Mongoose 中的 Schema 对象也是 JavaScript 中的对象,它通过调用 mongoose.Schema 方法来创建,并传递一个对象作为参数,包含每个属性字段的定义。每个属性的值都是一个对象,用于指定该属性的类型、是否必填、默认值、验证规则等等。
模型(Model)创建
定义好 Schema 之后,我们还需要通过模型(Model)来进行操作,它相当于是 Schema 的实例。我们可以通过调用 mongoose.model 方法来创建一个 Model 对象,并指定它所对应的文档名称、Schema 和集合(Collection),代码如下:
const User = mongoose.model('User', userSchema, 'users');这段代码创建了一个名为 User 的 Model 对象,它对应了一个名为 users 的集合(可以根据需要修改集合名称),同时关联了刚刚定义的 userSchema 对象。
钩子(Hooks)定义
有了用户的 Schema 和 Model,我们还需要定义一些钩子(Hooks)。钩子是一些操作的前后钩子函数,可以在对数据进行保存、更新、删除等操作时,指定一些特定的逻辑。
例如我们想要在每一次保存之前对密码进行加密处理,可以通过以下代码实现:
userSchema.pre('save', function (next) {
const user = this;
// encrypt password
// ...
next();
});这段代码定义了一个保存前的钩子函数,我们可以在里面做一些加密处理的操作,然后调用 next() 方法进行后续的操作。
中间件(Middleware)定义
除了钩子函数,Mongoose 还提供了另一个触发点,即中间件(Middleware)。中间件是对文档进行操作前后执行的一次操作,例如在保存之前对数据进行校验等。相对于钩子函数的触发点是固定的,中间件可以灵活地根据操作的需要来使用。
例如以下代码:
userSchema.pre('validate', function (next) {
const user = this;
if (user.email && !validator.isEmail(user.email)) {
return next(new Error('Invalid email'));
}
next();
});这段代码定义了一个校验前的中间件,我们在这里可以添加一些对数据的约束和检查。
查询定义
在实际的某些业务场景下,我们可能需要进行更为灵活的查询操作,例如查询某个范围内的用户等,Mongoose 提供了一些符号操作符,可以帮助我们更加灵活的进行数据库操作。
例如以下代码,可以查询所有名字带有 "John" 的用户,且注册时间在 2019 年之前或离目前时间不到 3 天的用户:
// www.javascriptcn.com code example
User.find({
$and: [
{ name: /John/ },
{
$or: [
{ createdAt: { $lt: new Date('2019-01-01T00:00:00Z') } },
{ createdAt: { $gt: new Date(Date.now() - 1000 * 60 * 60 * 24 * 3) } }
]
}
]
})我们在这里使用了 $and 和 $or 操作符,然后通过比较时间,来实现查询功能。
Populates
在使用 MongoDB 的时候,有时候我们在一张表中需要引用另一张表的数据。这个时候,我们就需要用到 populate() 方法。
例如,在我们的应用中,某个用户发表了一篇文章,并将文章的评论也存储在了数据库中。而试想,每当我们查询这篇文章时,都需要去查询并加载该文章下的所有评论,那么这个查询操作就会非常大而且低效。
这个时候,我们就可以使用 populate() 方法,将文章和评论所对应的集合进行关联,进行快速便捷的查询。
例如以下代码:
// www.javascriptcn.com code example
const User = mongoose.model('User', userSchema, 'users');
const Comment = mongoose.model('Comment', commentSchema, 'comments');
const Post = mongoose.model('Post', postSchema, 'posts');
const commentSchema = new mongoose.Schema({
text: { type: String, required: true },
user: { type: mongoose.Schema.Types.ObjectId, ref: 'User' }
});
const postSchema = new mongoose.Schema({
title: { type: String, required: true },
content: { type: String, required: true },
user: { type: mongoose.Schema.Types.ObjectId, ref: 'User' },
comments: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Comment' }]
});
Post.findById(postId)
.populate('user')
.populate({ path: 'comments', populate: { path: 'user' } })
.exec(callback);我们在这里定义了 3 个模型:User、 Comment、Post。其中 Post 中定义了一个数组,用于存储该文章下的评论。我们通过 populate() 方法,指定了 user 字段和 comments 字段,然后实现了对评论的嵌入式查询。
实战案例和建议
上面介绍了 Mongoose 的一些基本概念和使用方法,下面我们通过一些实际案例来综合运用这些概念和方法,以提高我们的开发效率和代码质量。
新增一条记录
通过 Mongoose,我们可以非常方便的使用 Model 对象来新增一条文档记录。
// www.javascriptcn.com code example
const user = new User({
username: 'test',
password: '123456',
email: 'test@example.com',
mobile: '12345678901'
});
user
.save()
.then(result => {
console.log(result);
})
.catch(error => {
console.log(error);
});查询所有记录
User.find()
.exec()
.then(users => {
console.log(users);
})
.catch(error => {
console.log(error);
});查询单个记录
User.findById(userId)
.exec()
.then(user => {
console.log(user);
})
.catch(error => {
console.log(error);
});更新记录
User.findByIdAndUpdate(userId, { $set: { username: 'newUsername' } })
.exec()
.then(user => {
console.log(user);
})
.catch(error => {
console.log(error);
});删除记录
User.findByIdAndRemove(userId)
.exec()
.then(user => {
console.log(user);
})
.catch(error => {
console.log(error);
});最后,我们给出以下几个建议:
- 建议全局唯一的 Mongoose 实例,以便在不同的模块中方便地共享连接、创建、Schema 和 Model。
- 建议为每个 Schema 和 Model 文件创建一个独立的文件,并在文件中将定义好的 Schema 和 Model 进行导出,可以方便管理和维护。
- 建议在每次导入 Model 的时候,使用
mongoose.connection.readyState来保证连接可用状态。 - 建议只有在出现问题时才使用 Promise.catch(),否则 Promise.then() 可以处理更多的业务情况。
结语
本文详细介绍了 Mongoose 的相关概念和用法,并通过实战案例和建议,帮助读者更好地掌握并应用 Mongoose 进行开发。希望本文能为读者提供实际帮助和指导。
Source: FunTeaLearn,Please indicate the source for reprints https://funteas.com/post/6781e9b9935627c900efa81f