Express Tutorial Part 3: Using a Database (with Mongoose)

2018-05-15 17:26 更新
先決條件: Express Tutorial Part 2: Creating a skeleton website
目的: 能夠使用Mongoose設(shè)計(jì)和創(chuàng)建自己的模型。

概述

圖書館工作人員將使用當(dāng)?shù)貓D書館網(wǎng)站存儲(chǔ)有關(guān)圖書和借閱者的信息,圖書館成員將使用它來瀏覽和搜索圖書,查看是否有任何副本,然后預(yù)訂或借閱。 為了有效地存儲(chǔ)和檢索信息,我們將其存儲(chǔ)在數(shù)據(jù)庫中。

Express應(yīng)用可以使用許多不同的數(shù)據(jù)庫,您可以使用多種方法來執(zhí)行 C reate, R ead, U pdate和 > D 電子(CRUD)操作。 本教程簡(jiǎn)要概述了一些可用選項(xiàng),然后詳細(xì)顯示所選的特定機(jī)制。

我可以使用什么數(shù)據(jù)庫?

應(yīng)用程序可以使用節(jié)點(diǎn)支持的任何數(shù)據(jù)( Express 本身沒有定義數(shù)據(jù)庫管理的任何特定附加行為/要求)。 許多常用選項(xiàng),包括PostgreSQL,MySQL,Redis,SQLite和MongoDB 。

在選擇數(shù)據(jù)庫時(shí),應(yīng)考慮時(shí)間 - 生產(chǎn)力/學(xué)習(xí)曲線,性能,易于復(fù)制/備份,成本,社區(qū)支持等。雖然沒有單一的"最佳"數(shù)據(jù)庫,但幾乎任何流行的解決方案 對(duì)于像我們的本地圖書館這樣的中小型網(wǎng)站應(yīng)該是可以接受的。

有關(guān)選項(xiàng)的詳細(xì)信息,請(qǐng)參閱:數(shù)據(jù)庫集成(Express docs)。

什么是與數(shù)據(jù)庫交互的最佳方式?

有兩種方法與數(shù)據(jù)庫交互:

  • Using the databases' native query language (e.g. SQL)
  • Using an Object Data Model ("ODM") / Object Relational Model ("ORM"). An ODM/ORM represents the website's data as JavaScript objects, which are then mapped to the underlying database. Some ORMs are tied to a specific database, while others provide a database-agnostic backend.

使用SQL或數(shù)據(jù)庫支持的任何查詢語言都可以獲得最佳的性能。 ODM通常較慢,因?yàn)樗鼈兪褂梅g代碼在對(duì)象和數(shù)據(jù)庫格式之間進(jìn)行映射,這可能不會(huì)使用最高效的數(shù)據(jù)庫查詢(如果ODM支持不同的數(shù)據(jù)庫后端,則尤其如此,并且必須在什么數(shù)據(jù)庫 支持功能)。

使用ORM的好處是程序員可以繼續(xù)思考JavaScript對(duì)象而不是數(shù)據(jù)庫語義 - 如果你需要使用不同的數(shù)據(jù)庫(在相同或不同的網(wǎng)站上),這是特別真實(shí)的。 它們還提供了執(zhí)行數(shù)據(jù)驗(yàn)證和檢查的顯而易見的地方。

提示:使用ODM / ORM通常會(huì)降低開發(fā)和維護(hù)成本! 除非您非常熟悉本機(jī)查詢語言或性能至關(guān)重要,否則應(yīng)該強(qiáng)烈考慮使用ODM。

我應(yīng)該使用什么ORM / ODM?

NPM軟件包管理器網(wǎng)站上提供了許多ODM / ORM解決方案(請(qǐng)查看 odm orm 標(biāo)記的子集!)。

在寫作時(shí)流行的一些解決方案是:

  • Mongoose: Mongoose is a MongoDB object modeling tool designed to work in an asynchronous environment.
  • Waterline: An ORM extracted from the Express-based Sails web framework. It provides a uniform API for accessing numerous different databases, including Redis, mySQL, LDAP, MongoDB, and Postgres.
  • Bookshelf: Features both promise-based and traditional callback interfaces, providing transaction support, eager/nested-eager relation loading, polymorphic associations, and support for one-to-one, one-to-many, and many-to-many relations. Works with PostgreSQL, MySQL, and SQLite3.
  • Objection: Makes it as easy as possible to use the full power of SQL and the underlying database engine (supports SQLite3, Postgres, and MySQL).

作為一般規(guī)則,在選擇解決方案時(shí),應(yīng)考慮提供的功能和"社區(qū)活動(dòng)"(下載,貢獻(xiàn),錯(cuò)誤報(bào)告,文檔質(zhì)量等)。 在編寫時(shí)Mongoose是目前最流行的ORM,如果你使用MongoDB為你的數(shù)據(jù)庫是一個(gè)合理的選擇。

使用Mongoose和MongoDb為L(zhǎng)ocalLibrary

對(duì)于 Local Library 示例(以及本主題的其余部分),我們將使用 "> Mongoose ODM 訪問我們的庫數(shù)據(jù)。 Mongoose充當(dāng) MongoDB 的前端,這是一個(gè)開源 > NoSQL 數(shù)據(jù)庫,它使用面向文檔的數(shù)據(jù)模型。 "文檔"的"集合",在MongoDB數(shù)據(jù)庫中,類似 到關(guān)系數(shù)據(jù)庫中的"行"的"表"。

這種ODM和數(shù)據(jù)庫組合在Node社區(qū)中非常受歡迎,部分原因是文檔存儲(chǔ)和查詢系統(tǒng)看起來非常像JSON,因此JavaScript開發(fā)人員很熟悉。

提示:您不需要知道MongoDB以使用Mongoose,但部分 "> Mongoose文檔 更容易使用,并了解您是否已經(jīng)熟悉MongoDB。

本教程的其余部分將介紹如何定義和訪問 LocalLibrary網(wǎng)站示例的Mongoose模式和模型。

設(shè)計(jì)LocalLibrary模型

在你跳入并開始編碼模型之前,值得花幾分鐘時(shí)間考慮我們需要存儲(chǔ)什么數(shù)據(jù)以及不同對(duì)象之間的關(guān)系。

我們知道我們需要存儲(chǔ)關(guān)于圖書的信息(標(biāo)題,摘要,作者,類型,ISBN),并且我們可能有多個(gè)副本可用(具有全球唯一的ID,可用性狀態(tài)等)。 我們可能需要存儲(chǔ)有關(guān)作者的更多信息,而不僅僅是他們的姓名,并且可能有多個(gè)作者使用相同或相似的名稱。 我們希望能夠根據(jù)書名,作者,類型和類別對(duì)信息進(jìn)行排序。

在設(shè)計(jì)模型時(shí),為每個(gè)"對(duì)象"(相關(guān)信息組)分別建立模型是有意義的。 在這種情況下,明顯的對(duì)象是書,書實(shí)例和作者。

您可能還想使用模型來表示選擇列表選項(xiàng)(例如,像下拉列表中的選項(xiàng)),而不是將選擇硬編碼到網(wǎng)站本身 - 這是建議當(dāng)所有的選項(xiàng)都不知道前面或可能 更改。 這種類型的模型的明顯候選是書類型(例如科幻小說,法國詩歌等)

一旦我們決定了我們的模型和領(lǐng)域,我們需要考慮它們之間的關(guān)系。

考慮到這一點(diǎn),下面的UML關(guān)聯(lián)圖顯示了我們?cè)谶@種情況下定義的模型(如框)。 如上所述,我們已經(jīng)創(chuàng)建了書的模型(書的通用細(xì)節(jié)),書實(shí)例(系統(tǒng)中可用的書的特定物理副本的狀態(tài))和作者。 我們還決定了一個(gè)類型模型,以便可以動(dòng)態(tài)創(chuàng)建值。 我們決定不要為 BookInstance:status 建立一個(gè)模型 - 我們將硬編碼可接受的值,因?yàn)槲覀儾幌M@些值發(fā)生變化。 在每個(gè)框中,您可以看到模型名稱,字段名稱和類型,以及方法及其返回類型。

該圖還示出了模型之間的關(guān)系,包括它們的多重性。 多重性是圖上的數(shù)字,其示出了可能存在于關(guān)系中的每個(gè)模型的數(shù)量(最大和最小)。 例如,框之間的連接線示出 Book Genre 是相關(guān)的。 靠近 Book 模型的數(shù)字表明一本圖書必須有零個(gè)或多個(gè) Genre (盡可能多),而下一行的另一端的數(shù)字 Genre 表示它可以有零個(gè)或多個(gè)關(guān)聯(lián)的圖書。

注意:如下面我們的 Mongoose入門中所述,通常最好是在一個(gè)模型中定義文檔/模型之間的關(guān)系的字段 (您仍然可以通過搜索其他模型中相關(guān)的 _id 找到相反的關(guān)系)。 下面我們選擇定義Book模式中的Book / Genre和Book / Author之間的關(guān)系,以及BookInstance模式中的Book / BookInstance之間的關(guān)系。 這個(gè)選擇有點(diǎn)武斷 - 我們可以同樣有在另一個(gè)模式中的字段。

注意:下一節(jié)提供了一個(gè)基本的說明,說明如何定義和使用模型。 當(dāng)你閱讀它,考慮我們將如何構(gòu)建上面的圖中的每個(gè)模型。

Mongoose引物

本節(jié)提供如何將Mongoose連接到MongoDB數(shù)據(jù)庫,如何定義模式和模型以及如何進(jìn)行基本查詢的概述。

請(qǐng)注意:此底稿受 Mongoose快速入門的影響很大 npm 官方文檔。

安裝Mongoose和MongoDB

Mongoose安裝在您的項(xiàng)目中( app.json ),就像使用NPM的任何其他依賴項(xiàng)一樣。 要安裝它,請(qǐng)?jiān)陧?xiàng)目文件夾中使用以下命令:

npm install mongoose --save

安裝 會(huì)添加所有的依賴項(xiàng),包括MongoDB數(shù)據(jù)庫驅(qū)動(dòng)程序,但它不會(huì)安裝MongoDB本身。 如果要安裝MongoDB服務(wù)器,則可以從此處下載安裝程序,以查看各種操作系統(tǒng)并安裝 它在本地。 您還可以使用基于云的MongoDB實(shí)例。

注意:在本教程中,我們將使用基于云端的mLab數(shù)據(jù)庫作為服務(wù) / plans / pricing /"> sandbox tier 來提供數(shù)據(jù)庫。 這適合開發(fā),并且對(duì)于教程是有意義的,因?yàn)樗?安裝"操作系統(tǒng)獨(dú)立(數(shù)據(jù)庫即服務(wù)也是您可能很好地用于生產(chǎn)數(shù)據(jù)庫的一種方法)。

連接到MongoDB

Mongoose 需要連接到MongoDB數(shù)據(jù)庫。 您可以 require()并使用 mongoose.connect()連接到本地托管數(shù)據(jù)庫,如下所示。

//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 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 獲取默認(rèn)的 Connection 對(duì)象。 連接后,將在 Connection 實(shí)例上觸發(fā)open事件。

提示:如果您需要?jiǎng)?chuàng)建其他連接,可以使用 mongoose.createConnection()。 這采用與 connect()相同的形式的數(shù)據(jù)庫URI(具有主機(jī),數(shù)據(jù)庫,端口,選項(xiàng)等)并返回 Connection 對(duì)象)。

定義和創(chuàng)建模型

使用 Schema 界面 定義模型。 模式允許您定義每個(gè)文檔中存儲(chǔ)的字段及其驗(yàn)證要求和默認(rèn)值。 此外,您可以定義靜態(tài)和實(shí)例幫助程序方法,以便更輕松地使用您的數(shù)據(jù)類型,以及虛擬屬性,您可以像任何其他字段一樣使用,但實(shí)際上并不存儲(chǔ)在數(shù)據(jù)庫中(我們將討論 下面一點(diǎn))。

然后使用 mongoose.model()方法將模式"編譯"成模型。 一旦你有一個(gè)模型,你可以使用它來查找,創(chuàng)建,更新和刪除給定類型的對(duì)象。

注意:每個(gè)模型都會(huì)映射到MongoDB數(shù)據(jù)庫中文檔集合。 文檔將包含在模型 Schema 中定義的字段/模式類型。

Defining schemas

下面的代碼片段顯示了如何定義一個(gè)簡(jiǎn)單的模式。 首先你 require() mongoose,然后使用Schema構(gòu)造函數(shù)創(chuàng)建一個(gè)新的模式實(shí)例,在構(gòu)造函數(shù)的對(duì)象參數(shù)中定義其中的各個(gè)字段。

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

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

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

在上面的例子中,我們只有兩個(gè)字段,一個(gè)字符串和一個(gè)日期。 在接下來的部分中,我們將展示一些其他字段類型,驗(yàn)證和其他方法。

Creating a model

使用 mongoose.model()方法從模式創(chuàng)建模型:

// Define schema
var SomeModelSchema = new mongoose.Schema({ name: 'string', });
// Compile model from schema
var SomeModel = mongoose.model('SomeModel', SomeModelSchema );

第一個(gè)參數(shù)是將為您的模型創(chuàng)建的集合的單一名稱(Mongoose將為上述模型 SomeModel 創(chuàng)建數(shù)據(jù)庫集合),第二個(gè)參數(shù)是您要使用的模式 在創(chuàng)建模型。

注意:定義模型類后,可以使用它們創(chuàng)建,更新或刪除記錄,并運(yùn)行查詢以獲取記錄或特定記錄子集。 我們將在使用模型部分以及創(chuàng)建視圖時(shí)向您展示如何執(zhí)行此操作。

Schema types (fields)

模式可以具有任意數(shù)量的字段 - 每個(gè)字段表示存儲(chǔ)在 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: Represents specific instances of a model in the database. For example, a book might use this to represent its author object. This will actually contain the unique ID (_id) for the specified object. We can use the populate() method to pull in the associated information when needed.
  • Mixed: An arbitrary schema type.
  • []: An array of items. You can perform JavaScript array operations on these models (push, pop, unshift, etc.). The examples above show?an array of objects without a specified type and an array of?String objects, but you can have an array of any type of object.

代碼還顯示了聲明字段的兩種方法:

  • Field name and type as a key-value pair (i.e. as done with fields name, binary and living).
  • Field name followed by an object defining the type, and any other options for the field. Options include things like:
    • default values.
    • built-in validators (e.g. max/min values) and custom validation functions.
    • Whether the field is required
    • Whether String fields should automatically be set to lowercase, uppercase, or trimmed (e.g. { type: String, lowercase: true, trim: true })

有關(guān)選項(xiàng)的詳情,請(qǐng)參閱: SchemaTypes (Mongoose文檔)。

Validation

Mongoose提供內(nèi)置和自定義驗(yàn)證器,以及同步和異步驗(yàn)證器。 它允許您在所有情況下指定可接受的范圍或值以及驗(yàn)證失敗的錯(cuò)誤消息。

內(nèi)置驗(yàn)證器包括:

  • All SchemaTypes have the built-in required validator. This is used to specify whether the field must be supplied in order to save a document.
  • Numbers have min and max validators.
  • Strings have:
    • enum: specifies the set of allowed values for the field.
    • match: specifies a regular expression that the string must match.
    • maxlength and minlength for the string.

下面的示例(略微修改了Mongoose文檔)顯示了如何指定一些驗(yàn)證器類型和錯(cuò)誤消息:


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

有關(guān)字段驗(yàn)證的完整信息,請(qǐng)參閱驗(yàn)證(Mongoose文檔)。

Virtual properties

虛擬屬性是您可以獲取和設(shè)置但不會(huì)持久保存到MongoDB的文檔屬性。 getter對(duì)于格式化或組合字段很有用,而setter對(duì)于將單個(gè)值解組成多個(gè)值以進(jìn)行存儲(chǔ)是有用的。 文檔中的示例構(gòu)造(并解構(gòu))來自名字和姓氏字段的全名虛擬屬性,這比在模板中使用時(shí)構(gòu)建全名更簡(jiǎn)單和更清楚。

注意:我們將使用庫中的虛擬屬性為每個(gè)模型記錄使用路徑和記錄的 _id 值定義唯一的網(wǎng)址。

有關(guān)詳情,請(qǐng)參閱虛擬(Mongoose文檔)。

Methods and query helpers

模式還可以具有實(shí)例方法, ://mongoosejs.com/docs/guide.html#statics">靜態(tài)方法 幫助">查詢助手。 實(shí)例和靜態(tài)方法是相似的,但有一個(gè)明顯的區(qū)別,即實(shí)例方法與特定記錄相關(guān)聯(lián),并且可以訪問當(dāng)前對(duì)象。 查詢助手允許您擴(kuò)展mongoose的可鏈接查詢構(gòu)建器API (例如,允許您添加查詢 "byName"除了 find() findOne() findById()方法)。

使用模型

一旦創(chuàng)建了模式,就可以使用它來創(chuàng)建模型。 模型表示數(shù)據(jù)庫中您可以搜索的文檔集合,而模型的實(shí)例表示可以保存和檢索的單個(gè)文檔。

我們?cè)谙旅嫣峁┮粋€(gè)簡(jiǎn)要概述。 有關(guān)詳情,請(qǐng)參閱:模型(Mongoose文檔)。

Creating and modifying documents

要?jiǎng)?chuàng)建記錄,您可以定義模型的實(shí)例,然后調(diào)用 save()。 下面的示例假設(shè)SomeModel是一個(gè)模型(具有單個(gè)字段"名稱"),我們已從我們的模式創(chuàng)建。

// 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!
});

請(qǐng)注意,創(chuàng)建記錄(以及更新,刪除和查詢)是異步操作 - 您提供在操作完成時(shí)調(diào)用的回調(diào)。 API使用錯(cuò)誤第一個(gè)參數(shù)約定,因此回調(diào)的第一個(gè)參數(shù)將始終為錯(cuò)誤值(或null)。 如果API返回一些結(jié)果,這將作為第二個(gè)參數(shù)提供。

您還可以使用 create()在保存模型實(shí)例的同時(shí)定義模型實(shí)例。 回調(diào)將返回第一個(gè)參數(shù)的錯(cuò)誤,并為第二個(gè)參數(shù)的新創(chuàng)建的模型實(shí)例返回錯(cuò)誤。

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

每個(gè)模型都有一個(gè)關(guān)聯(lián)的連接(這將是使用 mongoose.model()時(shí)的默認(rèn)連接)。 您創(chuàng)建一個(gè)新的連接并調(diào)用 .model()就可以在不同的數(shù)據(jù)庫上創(chuàng)建文檔。

您可以使用點(diǎn)語法訪問此新記錄中的字段,并更改值。 您必須調(diào)用 save() update()將修改的值存回?cái)?shù)據(jù)庫。

// 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!
?  });

Searching for records

您可以使用查詢方法搜索記錄,將查詢條件指定為JSON文檔。 下面的代碼片段顯示了如何在網(wǎng)球數(shù)據(jù)庫中找到所有運(yùn)動(dòng)員,僅返回運(yùn)動(dòng)員姓名 的字段。 這里我們只指定一個(gè)匹配字段(運(yùn)動(dòng)),但您可以添加更多的條件,指定正則表達(dá)式條件,或刪除條件,以返回所有運(yùn)動(dòng)員。

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.
})

如果指定回調(diào),如上所示,查詢將立即執(zhí)行。 回調(diào)將在搜索完成時(shí)調(diào)用。

注意:Mongoose中的所有回調(diào)都使用回調(diào)(error,result)模式。 如果在執(zhí)行查詢時(shí)發(fā)生錯(cuò)誤,則 error 參數(shù)將包含錯(cuò)誤文檔, result 將為null。 如果查詢成功,則 error 參數(shù)將為null,并且 result 將填充查詢結(jié)果。

如果您未指定回調(diào),API將返回類型為 Query >。 您可以使用此查詢對(duì)象構(gòu)建您的查詢,然后使用 exec()方法執(zhí)行(使用回調(diào))。

// 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
})

上面我們?cè)?code> find()方法中定義了查詢條件。 我們也可以使用 where()函數(shù)來做到這一點(diǎn),我們可以使用點(diǎn)運(yùn)算符(。)將查詢的所有部分鏈接在一起,而不是單獨(dú)添加它們。 下面的代碼片段與上面的查詢相同,有一個(gè)額外的年齡條件。

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()方法會(huì)獲取所有匹配的記錄,但通常您只想獲取 一個(gè)匹配。 以下方法查詢單個(gè)記錄:

請(qǐng)注意:還有 count()方法 您可以使用它來獲取符合條件的項(xiàng)目數(shù)。 如果要在不實(shí)際獲取記錄的情況下執(zhí)行計(jì)數(shù),這將非常有用。

有很多你可以做的查詢。 有關(guān)詳細(xì)信息,請(qǐng)參閱:查詢(Mongoose文檔)。

您可以使用 ObjectId 模式字段,或者從一個(gè)文檔到許多使用 ObjectIds 數(shù)組的一個(gè)文檔/模型實(shí)例到另一個(gè)文檔/模型實(shí)例創(chuàng)建引用。 該字段存儲(chǔ)相關(guān)模型的ID。 如果您需要相關(guān)文檔的實(shí)際內(nèi)容,可以使用填充() >方法在查詢中用實(shí)際數(shù)據(jù)替換id。

例如,以下模式定義了作者和故事。 每個(gè)作者可以有多個(gè)故事,我們表示為 ObjectId 的數(shù)組。 每個(gè)故事可以有一個(gè)作者。 "ref"(下面以粗體突出顯示)告訴模式哪個(gè)模型可以分配給此字段。

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 Person = mongoose.model('Author', authorSchema);

我們可以通過分配 _id 值來保存對(duì)相關(guān)文檔的引用。 下面我們創(chuàng)建一個(gè)作者,然后一本書,并將作者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
  });
});

我們的故事文檔現(xiàn)在有一個(gè)由作者文檔ID引用的作者。 為了在我們的故事結(jié)果中獲得作者信息,我們使用 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"
});

注意:精明的讀者會(huì)注意到我們?yōu)槲覀兊墓适绿砑恿艘晃蛔髡?,但我們沒有做任何事情將我們的故事添加到作者的 stories 數(shù)組中。 那么我們?nèi)绾文艿玫揭粋€(gè)特定作者的所有故事? 一種方法是將我們的作者添加到故事數(shù)組中,但這將導(dǎo)致我們有兩個(gè)地方需要維護(hù)有關(guān)作者和故事的信息。

更好的方法是獲取作者 _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.
});

這幾乎是您在使用本教學(xué)課程相關(guān)項(xiàng)目時(shí)需要了解的一切 。 有關(guān)詳細(xì)信息,請(qǐng)參閱人口(Mongoose文檔)。

每個(gè)文件一個(gè)模式/模型

雖然可以使用任何您喜歡的文件結(jié)構(gòu)創(chuàng)建模式和模型,但我們強(qiáng)烈建議在其自己的模塊(文件)中定義每個(gè)模型模式,導(dǎo)出方法以創(chuàng)建模型。 如下所示:

// 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 );

然后,您可以在其他文件中立即要求并使用模型。 下面我們展示如何使用它來獲取模型的所有實(shí)例。

//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);

設(shè)置MongoDB數(shù)據(jù)庫

現(xiàn)在我們了解了Mongoose可以做什么,以及我們?nèi)绾卧O(shè)計(jì)我們的模型,現(xiàn)在是開始在 LocalLibrary 網(wǎng)站上工作的時(shí)候了。 我們首先要做的是設(shè)置一個(gè)MongoDb數(shù)據(jù)庫,我們可以使用它來存儲(chǔ)我們的庫數(shù)據(jù)。

在本教程中,我們將使用 mLab 的免費(fèi)云托管的" > sandbox "數(shù)據(jù)庫。 此數(shù)據(jù)庫層不適合生產(chǎn)網(wǎng)站,因?yàn)樗鼪]有冗余,但它是偉大的開發(fā)和原型。 我們?cè)谶@里使用它,因?yàn)樗敲赓M(fèi)和容易設(shè)置,并且因?yàn)閙Lab是流行的數(shù)據(jù)庫作為服務(wù)供應(yīng)商,你可以合理地選擇為您的生產(chǎn)數(shù)據(jù)庫(當(dāng)時(shí)其他流行的選擇 的寫作包括撰寫, .html"> ScaleGrid MongoDB Atlas )。

注意:如果您愿意,可以在本地設(shè)置MongoDB數(shù)據(jù)庫,方法是下載并安裝 適合您系統(tǒng)的二進(jìn)制文件。 本文中的其余說明將類似,但連接時(shí)要指定的數(shù)據(jù)庫URL除外。

您首先需要使用mLab 創(chuàng)建帳戶(這是免費(fèi)的,只需輸入基本的聯(lián)系方式 并確認(rèn)其服務(wù)條款)。

登錄后,您將轉(zhuǎn)到首頁屏幕:

  1. Click Create New in the MongoDB Deployments section.
  2. This will open the Create new deployment screen.
    • Select Single-node in the Plan section
    • Select the Sandbox radio button in the Standard Line section
    • Enter a Database Name - for example "local_library"
  3. Click the Create new MongoDB deployment button.

    創(chuàng)建數(shù)據(jù)庫后,您將返回查看您的MongoDb部署的概述。height:266px; width:800px;">

  4. 單擊新數(shù)據(jù)庫(如上面突出顯示)以打開包含數(shù)據(jù)庫的更多詳細(xì)信息的視圖。 您可以看到數(shù)據(jù)庫沒有集合(數(shù)據(jù))。
    width:800px;">
    您需要用于訪問數(shù)據(jù)庫的URL顯示在上面的表單上(針對(duì)上面圈出的此數(shù)據(jù)顯示)。 為了使用這個(gè),你需要?jiǎng)?chuàng)建一個(gè)數(shù)據(jù)庫用戶,你可以在URL中指定。

  5. Click the Users tab and select the Add database user button.
  6. Enter a username and password (twice), and then press Create. Do not select Make read only.

您現(xiàn)在已經(jīng)創(chuàng)建了數(shù)據(jù)庫,并且具有可用于訪問它的URL(具有用戶名和密碼)。 這將看起來像: mongodb:// your_user_namer:your_password@ds119748.mlab.com:19748 / local_library 。

安裝Mongoose

打開命令提示符,并導(dǎo)航到您創(chuàng)建骨架本地圖書館網(wǎng)站的目錄。 輸入以下命令以安裝Mongoose(及其依賴項(xiàng))并將其添加到您的 package.json 文件中,除非您在閱讀 Mongoose Primer / a>。

npm install mongoose --save

連接到MongoDB

打開 /app.js (在項(xiàng)目的根目錄下),并在下面復(fù)制以下文本,您在此聲明 Express應(yīng)用程序?qū)ο?/em>( express(); )。 將數(shù)據(jù)庫url字符串(\' insert_your_database_url_here \')替換為您自己的數(shù)據(jù)庫的位置(即使用來自從mLab 的信息)。

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

如上所述,在上面的Mongoose引導(dǎo)中,此代碼創(chuàng)建與數(shù)據(jù)庫的默認(rèn)連接,并綁定到錯(cuò)誤事件(以便錯(cuò)誤將打印到控制臺(tái))。

定義LocalLibrary模式

我們將為每個(gè)模型定義一個(gè)單獨(dú)的模塊,如上所述的 。 首先在項(xiàng)目根目錄中為我們的模型創(chuàng)建一個(gè)文件夾( / models ),然后為每個(gè)模型創(chuàng)建單獨(dú)的文件:

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

作者模型

復(fù)制下面顯示的作者模式代碼,并將其粘貼到您的 ./ models / author.js 文件中。 該方案定義了作者對(duì)于第一個(gè)和家族名稱具有 String SchemaTypes,這是必需的并且最多有100個(gè)字符,以及 Date 死亡。

var mongoose = require('mongoose');

var Schema = mongoose.Schema;

var AuthorSchema = 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);

我們還針對(duì)名為"url"的類型聲明了一個(gè)虛擬,它返回獲取模型的特定實(shí)例所需的絕對(duì)網(wǎng)址 - 我們將使用 每當(dāng)我們需要獲得到特定作者的鏈接時(shí)。

請(qǐng)注意:在模式中將我們的網(wǎng)址聲明為虛擬網(wǎng)址是一個(gè)好主意,因?yàn)檫@樣一來,只需要在一個(gè)地方更改項(xiàng)目的網(wǎng)址。
此時(shí),使用此網(wǎng)址的鏈接 將不工作,因?yàn)槲覀儧]有任何路由處理代碼為個(gè)別模型實(shí)例。 我們將在后面的文章中設(shè)置它們!

在模塊的最后,我們導(dǎo)出模型。

書模型

復(fù)制下面顯示的 Book 模式代碼,并將其粘貼到您的 ./ models / book.js 文件中。 其中大部分類似于作者模型 - 我們已經(jīng)聲明了一個(gè)帶有多個(gè)字符串字段的模式和一個(gè)用于獲取特定書記錄的URL的模式,并且我們已經(jīng)導(dǎo)出了模型。

var mongoose = require('mongoose');

var Schema = mongoose.Schema;

var BookSchema = 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);

這里的主要區(qū)別是我們已經(jīng)創(chuàng)建了對(duì)其他模型的兩個(gè)引用(以粗體突出顯示):

  • author is a reference to a single Author model object, and is required.
  • genre is a reference to an array of Genre model objects. We haven't declared this object yet!

BookInstance模型

最后,復(fù)制下面顯示的 BookInstance 模式代碼,并將其粘貼到 ./ models / bookinstance.js 文件中。 BookInstance 表示某人可能借用的書的特定副本,并包括有關(guān)副本是否可用或其預(yù)期的日期,"印記"或版本詳細(xì)信息的信息。

var mongoose = require('mongoose');

var Schema = mongoose.Schema;

var BookInstanceSchema = 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);

我們?cè)谶@里顯示的新東西是字段選項(xiàng):

  • enum: This allows us to set the allowed values of a string. In this case we use it to specify the availability status of our books (using an enum means that we can prevent mis-spellings and arbitrary values for our status)
  • default: We use default to set the default status for newly created bookinstances to maintenance and the default due_back date to now (note how you can call the Date function when setting the date!)

其他一切都應(yīng)該從我們以前的模式熟悉。

類型模型 - 挑戰(zhàn)!

打開您的 ./ models / genre.js 文件,并創(chuàng)建存儲(chǔ)類型(圖書類別,例如它是小說還是非小說,浪漫或軍事歷史等)的架構(gòu)。

其定義將與其他模型非常相似:

  • The model should have a String SchemaType called name to describe the genre.
  • This name should be required and have between 3 and 100 characters.
  • Declare a virtual for the genre's URL, named url.
  • Export the model.

測(cè)試 - 創(chuàng)建一些項(xiàng)目

而已。 我們現(xiàn)在有所有型號(hào)的網(wǎng)站設(shè)置!

為了測(cè)試模型(并創(chuàng)建一些示例書和其他項(xiàng)目,我們可以在下一篇文章中使用),我們現(xiàn)在將運(yùn)行一個(gè)獨(dú)立腳本來創(chuàng)建每種類型的項(xiàng)目:

  1. Download (or otherwise create) the file populatedb.js in the root of your locallibrary website (in the level above your /models folder).

    注意:您不需要知道 "> populatedb.js 在這一點(diǎn)上工作。 我們將在后面的文章中解釋相關(guān)的部分。

  2. Enter the following commands in the project root to install the?async module?that is required by the script (we'll discuss this in later tutorials, )
    npm install async --save
  3. Run the script using node in your command prompt, passing in the URL of your MongoDB database:
    node populatedb <your mongodb url>????
  4. The script should run through to completion, displaying items as it creates them in the terminal.

提示: mLab 上轉(zhuǎn)到您的數(shù)據(jù)庫。 現(xiàn)在,您應(yīng)該可以深入查看圖書,作者,流派和BookInstances的各個(gè)集合,并查看單個(gè)文檔。

概要

在本文中,我們已經(jīng)了解了一些關(guān)于Node / Express上的數(shù)據(jù)庫和ORM,以及有關(guān)如何定義Mongoose模式和模型的內(nèi)容。 然后我們使用這些信息來設(shè)計(jì)和實(shí)現(xiàn) Book , BookInstance , Author Genre LocalLibrary 網(wǎng)站。

最后,我們通過創(chuàng)建多個(gè)實(shí)例(使用獨(dú)立腳本)來測(cè)試我們的模型。 在下一篇文章中,我們將介紹創(chuàng)建一些頁面來顯示這些對(duì)象。

也可以看看

以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)