本文简短介绍数据库,以及如何搭配Node/Express 应用使用数据库。接下来会演示我们如何使用 Mongoose ,为 本地图书馆 提供数据库存取。本文说明物件纲要与模型如何宣告、主要的栏位型态,以及基本验证。本文也简短演示一些存取模型数据的主要方法。

前置条件: Express 教程2: 创建一个骨架网站
目标: 能够使用Mongoose设计并创造自己的模型。

概览

图书馆职员会使用本地图书馆网站,存放书本和借书者信息。图书馆使用者会用网站浏览与寻找书本,看看是否有可以借阅的书本复本,然后预约或者借阅。为了有效率地存放与取用信息,我们将把它存放到数据库。

Express 应用可以使用许多不同的数据库,并且有好几种方法可以执行创建 Create、读取 Read、更新 Update 和删除 Delete (CRUD) 操作。本教程为一些可用的选项,提供简短的概览,然后接着详细演示该选项的特定运行机制。

我可以使用什么数据库?

Express 应用程序可以使用 Node 支持的任何数据库(Express 本身不会为数据库管理,定义任何特定的附加行为/要求)。有许多流行的选项,包括 PostgreSQL,MySQL,Redis,SQLite 和 MongoDB。

在选择数据库时,您应该考虑时间 - 生产力/学习曲线,性能,易复制/备份,成本,社区支持等等。虽然没有单一的 “最佳” 数据库,但几乎任何流行的解决方案,我们的本地图书馆这样的中小型网站,应该都可以接受。 

有关选项的更多信息,请参阅:数据库集成(Express docs)。

与数据库交互的最好方式是什么?

有两种与数据库交互的方法:

  • 使用数据库的原生查询语言(例如SQL)
  • 使用对象数据模型(“ODM”)/对象关系模型(“ORM”)。 ODM / ORM将网站的数据表示为JavaScript对象,然后将其映射到底层数据库。一些ORM绑定到特定的数据库,而另一些则提供了一个不特定数据库的后端。

通过使用 SQL 或数据库支持的任何查询语言,都可以获得最佳性能。ODM通常比较慢,因为它们使用翻译代码,在对象和数据库格式之间进行映射,这可能不会使用最有效的数据库查询(尤其是如果ODM支持不同的数据库后端,并且必须在各个数据库所支持的功能方面,做出更大的折衷)。

使用 ORM 的好处是,程序员可以继续用 JavaScript 对象而不是数据库语义来思考 — 如果您需要使用不同数据库(在相同或不同的网站上),那么尤其如此。他们还提供了一个明显的地方来执行数据验证和检查。

Tip:使用ODM / ORM通常可以降低开发和维护成本!除非您非常熟悉本地查询语言,或者性能对您至关重要,否则您应该强烈考虑使用 ODM。

我应该使用哪个 ORM/ODM ?

NPM 包管理器站点上,有许多ODM / ORM解决方案(查看 odmorm 标签的子集合!)。

在撰写本文时,受欢迎的几种解决方案是:

  • Mongoose: Mongoose是一个MongoDB对象建模工具,用于在异步环境中工作。
  • Waterline: 它是从基于Express的 Sails web 框架中提取的 ORM。它提供了一个统一的 API,来访问众多不同的数据库,包括Redis,mySQL,LDAP,MongoDB 和 Postgres。
  • Bookshelf:  提供基于 promise 和传统回调的接口,提供事务支持,eager/嵌套 eager 关系加载,多态关联以及对一对一,一对多和多对多关系的支持。适用于PostgreSQL,MySQL 和 SQLite3。
  • Objection: 以尽可能简单的方式,使用 SQL 的全部功能,和底层数据库引擎(支持SQLite3,Postgres 和 MySQL)。
  • Sequelize 是 Node.js 和 io.js 基于 promise 的 ORM。它支持以下数据库方言,PostgreSQL,MySQL,MariaDB,SQLite 和 MSSQL,并具有可靠的事务支持,关系,唯读复本等功能。

一般来说,在选择解决方案时,您应该考虑提供的功能和 “社区活动” (下载,贡献,错误报告,文档质量等)。在撰写本文时,Mongoose 是迄今为止最受欢迎的 ORM,如果您将MongoDB 用于你的数据库,那么它是一个合理的选择。

在本地图书馆使用 Mongoose 和 MongoDb

对于本地图书馆示例(以及本主题的其余部分),我们将使用 Mongoose ODM 来访问我们的图书馆数据。Mongoose 是 MongoDB 的前端,MongoDB 是一个使用面向文档数据模型的开源 NoSQL 数据库。在 MongoDB 数据库中,“文档” 的 “集合” ,类似于关系数据库中 “行” 的 “表”。

这种 ODM 和数据库的结合在 Node 社区中非常流行,部分原因是文档存储和查询系统,看起来非常像 JSON,因此对 JavaScript 开发人员来说很熟悉。

提示: 使用 Mongoose 时,您不需要事先了解 MongoDB,但是如果您已经熟悉 MongoDB,Mongoose文档的一部分会更易于使用和理解。

本教程的其余部分,将介绍如何为 本地图书馆网站示例,定义和访问Mongoose 模式和模型。

设计本地图书馆的模型

在您开始编写模型之前,花几分钟的时间思考,我们需要存储的数据以及不同对象之间的关系。

我们知道,我们需要存储有关书籍的信息(标题,摘要,作者,种类,国际标准书号),以及我们可能有多个副本可用(具有全域唯一ID,可用状态等)。我们可能需要存储有关作者的更多信息,而不仅仅是他们的名字,并且可能有多个作者,具有相同或相似的名称。我们希望能够根据书名,作者,种类和类别对信息进行分类。

在设计模型时,对于每个“对象”(相关信息组)都有独立的模型,是有意义的。在这种情况下,明显的对象是书籍,书籍实例和作者。

您可能还希望,使用模型来表示选择列表选项(例如,选择的下拉列表),而不是将选项硬编码到网站本身 — 在无法预先知道所有选项,或者可能更改时,更建议使用模型来表示。很明显的,书本类型是这种模型的可能人选(例如科幻小说,法国诗歌等)。

一旦我们决定了我们的模型和字段,我们就需要考虑它们之间的关系。

考虑到这一点,下面的 UML 关联图,显示了我们在这种情况下定义的模型(一个框对应一个模型)。如上所述,我们创建了以下模型,图书(本书的通用细节),书本实例(系统中可用图书的特定实际副本的状态)和作者。我们还决定建立一个种类模型,以便可以动态创建它的值,而不是将下拉选项硬编码。我们已经决定不为书本实例:状态 BookInstance:status 建立模型 — 我们将硬编码可接受的值,因为我们不希望这些值发生变化。在下图每个框中,您可以看到模型名称,字段名称和类型,以及方法及其返回类型。

下图还显示了模型之间的关系,包括它们的多重性。多重性是图中显示可能存在于关系中的每个模型的数量(最大值和最小值)的数字。例如,框之间的连接线,显示书本Book 和种类Genre是相关的。靠近书本Book模型的数字,表明一本书必须有零个或多个种类(您想要多少都可以),而种类Genre 旁边一行的数字,表明它可以有零个或多个相关书籍。

注意: 正如我们在下面的 Mongoose入门中所讨论的那样,通常只需要在一个模型中定义文档/模型之间关系的字段(通过在另一个模型中搜索相关的_id仍然可以找到反向关系)。下面我们选择在书本纲要(Book schema)中定义Book/Genre和Book/Author之间的关系,以及书本实例纲要(BookInstance Schema)中 Book/BookInstance之间的关系。这种选择有点武断 — 我们同样可以在其他纲要中拥有该字段。

Mongoose Library Model  with correct cardinality

注意: 下一节提供了一个基本的入门知识,解释如何定义和使用模型。在您阅读它时,请想想我们将如何构建上图中的每个模型。

Mongoose 入门

本节概述如何将 Mongoose 连接到 MongoDB 数据库,如何定义模型纲要和模型,以及如何进行基本查询。

注意: 本入门受到 npm 上的 Mongoose 快速入门Mongoose 官方文档的“深度影响”。

安装 Mongoose 和 MongoDB

Mongoose 像任何其他依赖项一样,安装在您的项目(package.json)中 — 使用NPM。要安装它,请在项目文件夹中,使用以下命令:

npm install mongoose

安装 Mongoose 会添加所有依赖项,包括 MongoDB 数据库驱动程序,但它不会安装 MongoDB 。如果你想安装一个 MongoDB 服务器,那么你可以从这里下载各种操作系统的安装程序,并在本地安装。您还可以使用基于云端的 MongoDB 实例。

注意: 对于本教程,我们将使用基于mLab云的数据库,作为服务沙箱层来提供数据库。这适用于开发,也对于本教程很有意义,因为它使“安装”与操作系统无关(数据库即服务,也是您可能会用于生产环境数据库的一种方法)。

连接到 MongoDB

Mongoose 需要连接到MongoDB 数据库。您可以require()并使用mongoose.connect(),以连接到本地托管的数据库,如下所示。

//Import the mongoose module
var mongoose = require('mongoose');

//Set up default mongoose connection
var mongoDB = 'mongodb://127.0.0.1/my_database';
mongoose.connect(mongoDB);
// Get Mongoose to use the global promise library
mongoose.Promise = global.Promise;
//Get the default connection
var db = mongoose.connection;

//Bind connection to error event (to get notification of connection errors)
db.on('error', console.error.bind(console, 'MongoDB connection error:'));

您可以使用mongoose.connection获取默认的Connection对象。一旦连接,在Connection实例上,将触发打开事件。

提示: 如果需要创建其他连接,可以使用mongoose.createConnection()。这与 connect()采用相同形式的数据库URI(包含主机,数据库,端口,选项等),并返回Connection对象。

定义并创建模型

模型使用Schema接口进行定义。 Schema 允许您定义存储在每个文档中的字段,及其验证要求和默认值。此外,您可以定义静态和实例助手方法,以更轻松地处理数据类型,以及可以像其他任何字段一样使用的虚拟属性,但实际上并不存储在数据库中(我们稍后将讨论)。

然后,纲要 Schemas 被mongoose.model()方法“编译” 为模型。拥有模型后,您可以使用它来查找,创建,更新和删除给定类型的对象。

注意: 每个模型都映射到 MongoDB 数据库中的文档集合。这些文档将包含模型纲要Schema中定义的字段/纲要型态。

定义纲要Schemas

下面的代码片段,显示了您可以如何定义一个简单的纲要。首先require() mongoose,然后使用 Schema 构造函数,创建一个新的Schema实例,在构造函数的对象参数中,定义其中的各个字段。

//Require Mongoose
var mongoose = require('mongoose');

//Define a schema
var Schema = mongoose.Schema;

var SomeModelSchema = new Schema({
    a_string: String,
    a_date: Date
});

在上面的例子中,我们只有两个字段,一个字符串和一个日期。在接下来的部分中,我们将展示一些其他的字段类型,验证和其他方法。

创建模型

使用mongoose.model() 方法从纲要创建模型:

// Define schema
var Schema = mongoose.Schema;

var SomeModelSchema = new Schema({
    a_string: String,
    a_date: Date
});

// Compile model from schema
var SomeModel = mongoose.model('SomeModel', SomeModelSchema );

第一个参数,是将为模型创建的集合的单数名称(Mongoose将为上面的SomeModel模型,创建数据库集合),第二个参数,是您要在创建模型时使用的纲要Shema。

注意: 定义模型类后,可以使用它们来创建,更新或删除记录,并运行查询,以获取记录的所有记录,或特定子集。我们将在以下“使用模型”部分,向您展示如何执行上述操作,以及当创建视图时,如何执行此操作。

纲要型态(字段)

纲要schema可以有任意数量的字段 — 每个字段代表存储在 MongoDB 文档中的字段。如下的示例纲要,显示许多常见字段类型及其声明方式。

var schema = new Schema(
{
  name: String,
  binary: Buffer,
  living: Boolean,
  updated: { type: Date, default: Date.now },
  age: { type: Number, min: 18, max: 65, required: true },
  mixed: Schema.Types.Mixed,
  _someId: Schema.Types.ObjectId,
  array: [],
  ofString: [String], // You can also have an array of each of the other types too.
  nested: { stuff: { type: String, lowercase: true, trim: true } }
})

大多数纲要型态 SchemaTypes(“type:”之后或字段名称之后的描述符)都是自解释的。例外情况是:

  • ObjectId: 表示数据库中模型的特定实例。例如,一本书可能会使用它来表示其作者对象。这实际上将包含指定对象的唯一ID (_id) 。我们可以使用 populate() 方法,在需要时提取相关信息。
  • Mixed: 任意纲要型态。
  • []: 一个数组的项目。您可以在这些模型上执行 JavaScript 数组操作(push,pop,unshift等)。上面的例子,显示了一个没有指定类型的对象数组,和一个 String 对象数组,但是你可以有任何类型的对象数组。

该代码还显示了声明一个字段的两种方式:

  • 字段名称和类型作为键值对(即是,像上面的name, binary and living)。
  • 字段名称后跟一个定义类型type的对象,以及该字段的任何其他选项。选项包括如下内容:
    • 默认值。
    • 内置验证器(例如最大/最小值)和自定义验证功能。
    • 该字段是否为必要
    • 是否应将字符串String字段自动设置为小写,大写或修剪(例如{ type: String, lowercase: true, trim: true })

有关选项的更多信息,请参阅SchemaTypes(Mongoose docs)。

验证

Mongoose 提供内置和自定义验证器,以及同步和异步验证器。它允许您在所有情况下,指定可接受的范围或值,以及验证失败的错误消息。

内置的验证器包括:

  • 所有 SchemaTypes都具有内置的必需验证器。这用于指定,是否必须提供该字段才能保存文档。
  • Numbers 数字有最小min和最大max验证器。
  • Strings 字符串有:
    • enum 枚举:指定该字段的允许值集合。
    • match: 指定字符串必须匹配的正则表达式。
    • 字符串的最大长度 maxlength 和最小长度 minlength

下面的示例(从Mongoose文档稍微修改)显示了如何指定一些验证器类型和错误消息:


    var breakfastSchema = new Schema({
      eggs: {
        type: Number,
        min: [6, 'Too few eggs'],
        max: 12
        required: [true, 'Why no eggs?']
      },
      drink: {
        type: String,
        enum: ['Coffee', 'Tea', 'Water',]
      }
    });

有关字段验证的完整信息,请参阅验证(Mongoose docs)。

虚拟属性

虚拟属性是您可以获取和设置的文档属性,但不会持久保存到MongoDB。getter 对格式化或组合字段非常有用,而 setter 可用于将单个值分解为多个值,以进行存储。

文档中的示例,从名字和姓氏字段构造(并解构)一个全名虚拟属性,这比每次在模板中使用全名更简单,更清晰。

注意: 我们将使用库中的虚拟属性,来为每个使用路径和记录的_id值的模型记录,定义唯一的URL。

欲了解更多信息,请参阅虚拟(Mongoose文档)。

方法和查询帮助

纲要schema也可以有实例方法静态方法查询助手。实例和静态方法很相似,但有明显的区别,即实例方法与特定记录相关联,并且可以访问当前对象。查询助手允许您扩展mongoose 的链式查询构建器API(例如,除了find(), findOne()findById()方法外,还允许您添加一个 “byName” 查询。

使用模型

一旦创建了纲要,就可以使用它来创建模型。该模型代表数据库中可以搜索的文档集合,而模型的实例代表您可以保存和检索的单个文档。

我们在下面简要介绍一下。有关更多信息,请参阅:模型(Mongoose docs)。

创建和修改文档

要创建记录,您可以定义模型的实例,然后调用save()。下面的例子假设,SomeModel 是我们从纲要创建的模型(带有单一字段 “name” )。

// Create an instance of model SomeModel
var awesome_instance = new SomeModel({ name: 'awesome' });

// Save the new model instance, passing a callback
awesome_instance.save(function (err) {
  if (err) return handleError(err);
  // saved!
});

创建记录(以及更新,删除和查询)是异步操作 — 您提供在操作完成时调用的回调。API使用错误优先参数约定,因此回调的第一个参数将始终为错误值(或null)。如果API返回一些结果,则将作为第二个参数提供。

您还可以使用create(),同时定义模型实例,并保存模型实例。回调将为第一个参数返回错误,为第二个参数返回新创建的模型实例。

SomeModel.create({ name: 'also_awesome' }, function (err, awesome_instance) {
  if (err) return handleError(err);
  // saved!
});

每个模型都有一个关联的连接(当您使用 mongoose.model()时,这将成为默认连接)。您创建一个新连接并调用.model(),以在另一个数据库上创建文档。您可以使用点语法访问此新记录中的字段,并更改值。您必须调用 save()update() ,将修改的值存回数据库。

// Access model field values using dot notation
console.log(awesome_instance.name); //should log 'also_awesome'

// Change record by modifying the fields, then calling save().
awesome_instance.name="New cool name";
awesome_instance.save(function (err) {
   if (err) return handleError(err); // saved!
   });

寻找纪录

可以使用查询方法搜索记录,将查询条件指定为 JSON 文档。下面的代码片段,显示了如何在数据库中,找到所有参加网球运动的运动员,只返回运动员姓名和年龄的字段。这里我们只指定一个匹配的字段(运动),但您可以添加更多条件,指定正则表达式标准,或完全删除条件以返回所有运动员。

var Athlete = mongoose.model('Athlete', yourSchema);

// find all athletes who play tennis, selecting the 'name' and 'age' fields
Athlete.find({ 'sport': 'Tennis' }, 'name age', function (err, athletes) {
  if (err) return handleError(err);
  // 'athletes' contains the list of athletes that match the criteria.
})

如果您指定回调,如上所示,查询将立即执行。搜索完成后将调用回调。

注意: Mongoose中的所有回调,都使用此回调模式callback(error, result)。如果执行查询时发生错误,错误参数error将包含错误文档,并且结果result将为null。如果查询成功,则error参数将为null,并且结果result 将被填充到查询结果。

如果您未指定回调,则API将返回Query类型的变量。您可以使用此查询对象来构建查询,然后稍后使用exec()方法执行(使用回调)。

// find all athletes that play tennis
var query = Athlete.find({ 'sport': 'Tennis' });

// selecting the 'name' and 'age' fields
query.select('name age');

// limit our results to 5 items
query.limit(5);

// sort by age
query.sort({ age: -1 });

// execute the query at a later time
query.exec(function (err, athletes) {
  if (err) return handleError(err);
  // athletes contains an ordered list of 5 athletes who play Tennis
})

上面我们在find()方法中,定义了查询条件。我们也可以使用

where()函数来执行此操作,并且我们可以使用点运算符( . )将查询的所有部分链接在一起,而不是分别添加它们。

下面的代码片段,与我们上面的查询相同,并有年龄的附加条件。

Athlete.
  find().
  where('sport').equals('Tennis').
  where('age').gt(17).lt(50).  //Additional where query
  limit(5).
  sort({ age: -1 }).
  select('name age').
  exec(callback); // where callback is the name of our callback function.

find() 方法获取所有匹配的记录,但通常你只想获得一个匹配。以下方法可以查询单个记录:

注意: 还有一个count()方法,您可以使用它来获取与条件匹配的项目数。如果您想要在不实际提取记录的情况下执行计数,这非常有用。

查询可以做更多的事情。有关更多信息,请参阅:查询(Mongoose文档)。

运用相关文档 — population方法

您可以使用 ObjectId 纲要字段,从一个文档/模型实例,创建一对一引用,或者使用ObjectIds数组,从一个文档创建一对多的引用。该字段存储相关模型的ID。如果需要关联文档的实际内容,可以在查询中使用populate() 方法,将 id 替换为实际数据。

例如,以下纲要定义作者和故事。每个作者可以有多个故事,我们将其表示为一个ObjectId数组。每个故事可以有一个作者。纲要从 “ref”(以粗体突出显示)得知,可以分配给该字段的模型。

var mongoose = require('mongoose')
  , Schema = mongoose.Schema

var authorSchema = Schema({
  name    : String,
  stories : [{ type: Schema.Types.ObjectId, ref: 'Story' }]
});

var storySchema = Schema({
  author : { type: Schema.Types.ObjectId, ref: 'Author' },
  title    : String
});

var Story  = mongoose.model('Story', storySchema);
var Author = mongoose.model('Author', authorSchema);

我们可以通过分配_id值,来保存对相关文档的引用。下面我们创建一个作者,然后创建一个故事,并将作者ID分配给我们的故事作者字段。

var bob = new Author({ name: 'Bob Smith' });

bob.save(function (err) {
  if (err) return handleError(err);

  //Bob now exists, so lets create a story
  var story = new Story({
    title: "Bob goes sledding",
    author: bob._id    // assign the _id from the our author Bob. This ID is created by default!
  });

  story.save(function (err) {
    if (err) return handleError(err);
    // Bob now has his story
  });
});

我们的故事文档,现在有作者文档ID引用的作者。为了在我们的故事结果中,获取作者信息,我们使用populate(),如下所示。

Story
.findOne({ title: 'Bob goes sledding' })
.populate('author') //This populates the author id with actual author information!
.exec(function (err, story) {
  if (err) return handleError(err);
  console.log('The author is %s', story.author.name);
  // prints "The author is Bob Smith"
});

注意: 敏锐的读者会注意到,我们在故事中添加了作者,但我们没有做任何事情,来将我们的故事添加到作者的故事stories数组中。那么我们怎样才能得到特定作者的所有故事?

一种方法,是将作者添加到故事数组中,但这会导致我们需要在两个地方,维护与作者和故事有关的信息。更好的方法是获取作者的_id,然后使用find(),在所有故事的作者字段中搜索此内容。

Story
.find({ author : bob._id })
.exec(function (err, stories) {
  if (err) return handleError(err);
  // returns all stories that have Bob's id as their author.
});

这几乎是您在本教程中,使用相关项目时,需要了解的所有内容。有关更多详细信息,请参阅 Population(Mongoose docs)。

一个档案对应一个纲要/模型

虽然您可以使用任何喜欢的文件结构创建纲要和模型,但我们强烈建议在每个模型模块(文件)中,定义每个模型纲要,导出方法以创建模型。如下所示:

// File: ./models/somemodel.js

//Require Mongoose
var mongoose = require('mongoose');

//Define a schema
var Schema = mongoose.Schema;

var SomeModelSchema = new Schema({
    a_string          : String,
    a_date            : Date,
});

//Export function to create "SomeModel" model class
module.exports = mongoose.model('SomeModel', SomeModelSchema );

然后,您可以在其他文件中,立即要求并使用该模型。下面我们展示如何使用它,来获取模型的所有实例。

//Create a SomeModel model just by requiring the module
var SomeModel = require('../models/somemodel')

// Use the SomeModel object (model) to find all SomeModel records
SomeModel.find(callback_function);

架设 MongoDB 数据库

现在我们了解了Mongoose能做什么,以及我们想如何设计我们的模型,现在该开始在LocalLibrary网站上工作了。

我们想要做的第一件事,就是设置一个MongoDb数据库,我们可以使用它来存储我们的图书馆数据。

本教程,我们将使用mLab免费的云托管的“沙盒”数据库。这个数据库层不适合生产环境的网站,因为它没有冗余设计,但它对于开发和原型设计来说非常有用。

我们在这里使用它,是因为它免费且易于设置,并且因为作为数据库服务供应商来说,mLab是流行的数据库选择之一,您可能会合理选择您的生产环境数据库(撰写本文时,其他流行的选择包括ComposeScaleGridMongoDB Atlas)。

注意: 如果您愿意,可以下载并安装与系统相对应的二进制文件,在本地设置 MongoDb 数据库。除了您在连接时指定的数据库URL之外,本文中的其余指令将很类似。

您首先需要使用mLab创建一个账户(这是免费的,只需要输入基本联系信息,并确认其服务条款)。

登录后,您将进入mLab主屏幕:

  1. 单击 MongoDB Deployments 部分中的Create New。
  2. 这将打开“云提供商”Cloud Provider 选择屏幕。
    MLab - screen for new deployment
     
    • 从“计划类型”Plan Type 部分中,选择“SANDBOX(免费)”计划。
    • 从“云提供商”Cloud Provider部分,选择任意提供商。不同的提供商,提供不同的地区(显示在选定的计划类型下面)。
    • 单击“继续”Continue按钮。
  3. 这将打开“选择区域”Select Region屏幕。

    Select new region screen

    • 选择离您最近的地区,然后选择继续 Continue.

  4. 这将打开Final Details屏幕。
    New deployment database name

    • 输入新数据库的名称local_library,然后选择继续Continue

  5. 这将打开订单确认屏幕。
    Order confirmation screen

    • 单击“提交订单”Submit Order以创建数据库。

  6. 您将返回到主屏幕。单击刚刚创建的新数据库,以打开其详细信息屏幕。正如你所看到的,数据库没有集合(数据)。

    mLab - Database details screen
     
    您需要用来访问数据库的URL,显示在上面的表单中(如上图所示)。为了使用它,您需要创建一个可以在URL中指定的数据库用户。

  7. 单击用户Users选项卡,并选择添加数据库用户按钮Add database user
  8. 输入用户名和密码(两次),然后按创建Create。不要选择只读read-only

您现在已经创建了数据库,并且有一个可以用来访问它的URL(带有用户名和密码)。这看起来像是这样的:mongodb://your_user_namer:your_password@ds119748.mlab.com:19748/local_library.

安装 Mongoose

打开命令提示符,并到您创建本地图书馆骨架网站的目录。输入以下命令,安装Mongoose(及其依赖项),并将其添加到您的package.json文件中,除非您在阅读上述Mongoose 入门时,已经这样做了。

npm install mongoose

连接到 MongoDB

打开/app.js(位于项目的根目录),并在声明Express应用程序对象的位置(在var app = express();之后)复制以下文本。将数据库url字符串('insert_your_database_url_here')替换为表示您自己的数据库的位置URL(即是使用来自上面mLab的信息)。

//Set up mongoose connection
var mongoose = require('mongoose');
var mongoDB = 'insert_your_database_url_here';
mongoose.connect(mongoDB);
mongoose.Promise = global.Promise;
var db = mongoose.connection;
db.on('error', console.error.bind(console, 'MongoDB connection error:'));

正如上面的 Mongoose入门 中所讨论的,此代码创建了与数据库的默认连接,并绑定到错误事件(以便将错误打印到控制台)。

定义本地图书馆纲要

如上所述,我们将为每个模型定义一个单独的模块。首先在项目根目录(/models)中,为我们的模型创建一个文件夹,然后为每个模型创建单独的文件:

/express-locallibrary-tutorial  //the project root
  /models
    author.js
    book.js
    bookinstance.js
    genre.js

作者模型

复制下面显示的Author作者纲要代码,并将其粘贴到./models/author.js文件中。该纲要定义了一个作者,具有String SchemaTypes的第一个名称和家族名称,这是必需的,最多有100个字符,Date字段为出生和死亡日期。

var mongoose = require('mongoose');

var Schema = mongoose.Schema;

var AuthorSchema = new Schema(
  {
    first_name: {type: String, required: true, max: 100},
    family_name: {type: String, required: true, max: 100},
    date_of_birth: {type: Date},
    date_of_death: {type: Date},
  }
);

// Virtual for author's full name
AuthorSchema
.virtual('name')
.get(function () {
  return this.family_name + ', ' + this.first_name;
});

// Virtual for author's URL
AuthorSchema
.virtual('url')
.get(function () {
  return '/catalog/author/' + this._id;
});

//Export model
module.exports = mongoose.model('Author', AuthorSchema);

我们还为AuthorSchema,声明了一个名为“url”的虚拟属性,它返回获取模型的特定实例所需的绝对URL  — 每当我们需要获取指向特定作者的链接时,我们将在模板中使用该属性。

注意: 在纲要中声明我们的URL是虚拟的,这是一个好主意,因为一个项目的URL只需要在一个地方更改。此时,使用此URL的链接将不起作用,因为我们还没有任何路由,可以处理个别模型实例的代码。我们将在后面的文章中介绍这些内容!

在模块的最后,我们导出了模型。

书本模型

复制下面显示的Book纲要代码,并将其粘贴到./models/book.js 文件中。其中大部分与作者模型相似 — 我们已经声明了一个具有多个字符串字段的纲要,以及一个虚拟属性,用于获取特定书籍记录的URL,并且我们已经导出了模型。

var mongoose = require('mongoose');

var Schema = mongoose.Schema;

var BookSchema = new Schema(
  {
    title: {type: String, required: true},
    author: {type: Schema.ObjectId, ref: 'Author', required: true},
    summary: {type: String, required: true},
    isbn: {type: String, required: true},
    genre: [{type: Schema.ObjectId, ref: 'Genre'}]
  }
);

// Virtual for book's URL
BookSchema
.virtual('url')
.get(function () {
  return '/catalog/book/' + this._id;
});

//Export model
module.exports = mongoose.model('Book', BookSchema);

这里的主要区别,是我们已经创建了两个对其他模型的引用:

  • 作者是对单个Author作者模型对象的引用,并且是必要的。
  • 种类是对Genre种类模型对象数组的引用。我们还没有宣告这个对象!

书本实例模型

最后,复制下面显示的BookInstance纲要代码,并将其粘贴到./models/bookinstance.js 文件中。 BookInstance表示某人可能借阅的书籍的特定副本,并包含有关该副本是否可用,或预期返回日期的信息,“印记”或版本详细信息。

var mongoose = require('mongoose');

var Schema = mongoose.Schema;

var BookInstanceSchema = new Schema(
  {
    book: { type: Schema.ObjectId, ref: 'Book', required: true }, //reference to the associated book
    imprint: {type: String, required: true},
    status: {type: String, required: true, enum: ['Available', 'Maintenance', 'Loaned', 'Reserved'], default: 'Maintenance'},
    due_back: {type: Date, default: Date.now}
  }
);

// Virtual for bookinstance's URL
BookInstanceSchema
.virtual('url')
.get(function () {
  return '/catalog/bookinstance/' + this._id;
});

//Export model
module.exports = mongoose.model('BookInstance', BookInstanceSchema);

我们在这里展示的新东西,是字段选项:

  • 枚举enum: 这允许我们设置字符串的允许值。在这种情况下,我们用它来指定我们书籍的可用性状态(使用枚举,意味着我们可以防止错误拼写和任意值,成为我们的状态)
  • 默认值default: 我们使用默认值,将新创​​建的书本实例的默认状态,设置为维护,并将默认的due_back日期,设置为现在now(请注意在设置日期时,如何调用Date函数!)

其他所有内容,大伙应该在前面教程里边已经熟悉了。

种类模型 - 自我挑战!

打开你的./models/genre.js文件,并创建一个存储类型的纲要(书本的类别,例如它是小说还是非小说,浪漫史或军事历史等)。

该定义将与其他模型非常相似:

  • 该模型应该有一个名为nameString SchemaType ,来描述种类。
  • 这个name字段应该是必要的,并且有3到​​100个字符。
  • 为类型的URL声明虚拟,名为url
  • 导出模型。

测试 — 创建一些项目

就是这样。我们现在已经为该网站建立了所有模型!

为了测试这些模型(并创建一些示例书籍,和其他项目以便于我们在后面文章使用),现在我们将运行一个独立的脚本来创建每种类型的项目:

  1. 在express-locallibrary-tutorial目录下(与package.json处于同一级别),下载(或以其他方式创建)文件 populatedb.js

    注意:您不需要知道 populatedb.js 的工作原理;它只是将示例数据添加到数据库中。

  2. 在项目根目录中,输入以下命令,以安装脚本所需的异步模块(我们将在后面的教程中讨论这一点)
    npm install async
  3. 在命令提示符下,使用 node 运行此脚本,传递MongoDB数据库的URL(与之前在app.js中替换insert_your_database_url_here占位符的那个相同):
    node populatedb <your mongodb url>​​​​
  4. 该脚本应一路运行至完成,并在终端中创建它们时显示各项目。

提示:mLab上的数据库。您现在应该可以深入到书本籍,作者,种类和书本实例的各个集合中,并查看单个文档。

总结

本文中我们学到了一点数据库和 Node/Express 的 ORMs,更多的是关于如何定义 Mongoose 纲要与模型。然后我们使用这些知识,为本地图书馆网站设计并实作出书本 Book, 书本实例BookInstance,作者 Author 和种类 Genre 模型。

最后,我们创建一些实例,以测试模型 (使用独立运作的命令稿)。下一篇文章,我们将关注于如何创建一些网页,以呈现这些物件。

 

另見

 

本系列教程

 

文档标签和贡献者

此页面的贡献者: edgar-chen
最后编辑者: edgar-chen,