建立一个 MongoDB Atlas 数据库并导入连接到它所需的软件包。将 mongodb@~3.6.0
和 mongoose@~5.4.0
添加到项目的 package.json
中。 然后,在 myApp.js
文件中请求 mongoose
。 创建一个 .env
文件,给它添加一个 MONGO_URI
变量。 变量的值为 MongoDB Atlas 数据库 URI。 应用单引号或双引号包裹 URI。请记住,环境变量 =
两边不能有空格。 例如应该这样写:MONGO_URI='VALUE'
。 完成后,使用下面的代码来连接数据库。
mongoose.connect(<Your URI>, { useNewUrlParser: true, useUnifiedTopology: true });
const mongoose = require('mongoose');
mongoose.connect("mongodb+srv://<username>:<password>@cluster0.zbwns.mongodb.net/myFirstDatabase?retryWrites=true&w=majority", { useNewUrlParser: true, useUnifiedTopology: true });
C RUD 第一小节——CREATE
首先,需要一个 Schema, 每一个 Schema 都对应一个 MongoDB 的 collection, 并且在相应的 collection 里定义 documents 的“样子”。 Schema 用于组成模型(Model), 可以通过嵌套 Schema 来创建复杂的模型。可以根据模型创建实例,模型实例化后的对象称为 documents。
handler 函数会在特定事件(比如调用服务器 API)发生时执行。 done()
是一个回调函数,它的作用是在一个异步操作(比如对数据库进行插入、查询、更新或删除)执行完成时,告知可以继续执行后续的其它代码。 这与 Node.js 中的处理方式十分类似,在 Node.js 中,在(异步操作)成功时调用 done(null, data)
,在失败时调用 done(err)
。
注意:与远程服务器进行交互时,需要考虑到发生错误的可能!
/* Example */
const someFunc = function(done) {
//... do something (risky) ...
if (error) return done(error);
done(null, result);
};
按下面的原型信息创建一个名为 personSchema
的 schema:
- Person Prototype -
--------------------
name : string [required]
age : number
favoriteFoods : array of strings (*)
采用 Mongoose 基础 schema 类型。 如果还想添加更多的键,就请使用 required 或 unique 等简单的验证器(validators),并设置默认值。 详情请参考 Mongoose 文档。
请从 personSchema
创建一个名为 Person
的 model。
const Schema = mongoose.Schema;
const personSchema = new Schema({
name: { type: String, required: true },
age: Number,
favoriteFoods: [String]
});
const Person = mongoose.model("Person", personSchema);
在 createAndSavePerson
函数中,使用在上一个挑战中写好的 Person
构造函数创建 document 实例, 将包含 name
、age
和 favoriteFoods
的对象传给构造函数, 这些属性的数据类型必须符合在 personSchema
中定义的类型。 然后在返回的 document 实例上调用方法 document.save()
。 同时,按 Node.js 的方式为它传一个回调函数。 这是一种常见模式,以下所有 CRUD 方法都将这样的回调函数作为最后一个参数。
/* Example */
// ...
person.save(function(err, data) {
// ...do your stuff here...
});
let createAndSavePerson = function(done) {
let janeFonda = new Person({name: "Jane Fonda", age: 84, favoriteFoods: ["eggs", "fish", "fresh fruit"]});
janeFonda.save(function(err, data) {
if (err) return console.error(err);
done(null, data)
});
};
在一些情况下,比如进行数据库初始化,会需要创建很多 model 实例来用作初始数据。 Model.create()
接受一组像 [{name: 'John', ...}, {...}, ...]
的数组作为第一个参数,并将其保存到数据库。
let arrayOfPeople = [
{name: "Frankie", age: 74, favoriteFoods: ["Del Taco"]},
{name: "Sol", age: 76, favoriteFoods: ["roast chicken"]},
{name: "Robert", age: 78, favoriteFoods: ["wine"]}
];
let createManyPeople = function(arrayOfPeople, done) {
Person.create(arrayOfPeople, function (err, people) {
if (err) return console.log(err);
done(null, people);
});
};
C R UD 第二小节—— Read 查询
尝试一种最简单的用法,Model.find()
接收一个查询 document(一个 JSON 对象)作为第一个参数,一个回调函数作为第二个参数, 它会返回由匹配到的数据组成的数组。 这个方法支持很多搜索选项, 详情请参阅文档。
let findPeopleByName = function(personName, done) {
Person.find({name: personName}, function (err, personFound) {
if (err) return console.log(err);
done(null, personFound);
});
};
Model.findOne()
与 Model.find()
十分类似,但就算数据库中有很多条数据可以匹配查询条件,它也只返回一个 document,而不会返回一个数组, 如果查询条件是声明为唯一值的属性,它会更加适用。
let findOneByFood = function(food, done) {
Person.findOne({favoriteFoods: food}, function (err, data) {
if (err) return console.log(err);
done(null, data);
});
};
在保存 document 的时候,MongoDB 会自动为它添加 _id
字段,并给该字段设置一个唯一的仅包含数字和字母的值。 通过 _id
搜索是一个十分常见的操作,为此,Mongoose 提供了一个专门的方法。
var findPersonById = function(personId, done) {
Person.findById(personId, function (err, data) {
if (err) return console.log(err);
done(null, data);
});
};
CR U D 第三小节—— Update 更新
在过去,如果想要编辑 document 并以某种方式使用它(比如放到服务器的返回数据中),就必须执行查找、编辑和保存。 Mongoose 有一个专用的更新方法 Model.update()
, 它与底层的 mongo 驱动绑定。 通过这个方法,可以批量编辑符合特定条件的多个 document。但问题在于,这个方法不会返回更新后的 document,而是返回状态信息。 此外,它直接调用 mongo 的底层驱动,让处理 model 的验证变得更加棘手。
const findEditThenSave = (personId, done) => {
const foodToAdd = 'hamburger';
Person.findById(personId, (err, person) => {
if(err) return console.log(err); person.favoriteFoods.push(foodToAdd);
person.save((err, updatedPerson) => {
if(err) return console.log(err);
done(null, updatedPerson)
})
})
};
最近发布的 mongoose 版本简化了 document 的更新方式, 但同时,一些高级功能(如 pre/post hook, 验证)的使用方式也变得和以前不同。因此,在很多情景下,上一个挑战中提到的老方法其实更常用。 新方法的加入,可以让我们使用 findByIdAndUpdate()
来进行基于 id 的搜索。
const findAndUpdate = (personName, done) => {
const ageToSet = 20;
Person.findOneAndUpdate({name: personName}, {age: ageToSet}, {new: true}, (err, updatedDoc) => {
if(err) return console.log(err);
done(null, updatedDoc);
})
};
提示: 需要返回更新后的 document。 可以把 findOneAndUpdate()
的第三个参数设置为 { new: true }
。 默认情况下,这个方法会返回修改前的数据。
CRU D 第四小节—— Delete 删除
findByIdAndRemove
和 findOneAndRemove
类似于之前的更新方法, 它们将被删除的 document 传给数据库。 和之前一样,使用函数参数 personId
作为查询关键字。
修改 removeById
函数,通过 _id
删除一个人的数据, 可以使用 findByIdAndRemove()
或 findOneAndRemove()
方法。
let removeById = function(personId, done) {
Person.findByIdAndRemove(
personId,
(err, removedDoc) => {
if(err) return console.log(err);
done(null, removedDoc);
}
);
};
Model.remove()
可以用于删除符合给定匹配条件的所有 document。
修改 removeManyPeople
函数,使用 nameToRemove
删除所有姓名是变量 Model.remove()
的人。 给它传入一个带有 name
字段的查询 document 和一个回调函数。
注意: Model.remove()
不会返回被删除的 document,而是会返回一个包含操作结果以及受影响的数据数量的 JSON 对象。 不要忘记将它传入 done()
回调函数,因为我们需要在挑战的测试中调用它。
const removeManyPeople = (done) => {
const nameToRemove = "Mary";
Person.remove({name: nameToRemove}, (err, response) => {
if(err) return console.log(err);
done(null, response);
})
};
如果不给 Model.find()
(或者别的搜索方法)的最后一个参数传入回调函数, 查询将不会执行。 可以将查询条件存储在变量中供以后使用, 也可以通过链式调用这类变量来构建新的查询字段。 实际的数据库操作会在最后调用 .exec()
方法时执行。 必须把回调函数传给最后一个方法。 Mongoose 提供了许多辅助查询函数, 这里使用最常见的一种。
修改 queryChain
函数来查询喜欢 foodToSearch
食物的人。 同时,需要将查询结果按 name
属性排序, 查询结果应限制在两个 document 内,并隐藏 age 属性。 请链式调用 .find()
、.sort()
、.limit()
和 .select()
,并在最后调用 .exec()
, 并将 done(err, data)
回调函数传入 exec()
。
let queryChain = function(done) {
var foodToSearch = "burrito";
var jsonObject = {favoriteFoods : foodToSearch};
Person.find(jsonObject).sort({name: 1}).limit(2).select({age: 0}).exec((err, data) => {
(err) ? done(err) : done(null, data);
})
};