Bookshelf.js:一個 Node.js ORM
在 Node.js(主要是一種以 Web 為中心的語言)之類的語言中,您將與之交互的最常見資源之一是數據庫。由於 SQL 是所有不同類型中最常見的一種,因此您需要一個好的庫來幫助您與它及其眾多特性進行交互。
Bookshelf.js 是最受歡迎的 Node.js ORM 包之一。它源於 Knex.js,這是一個靈活的查詢構建器,可與 PostgreSQL、MySQL 和 SQLite3 一起使用。 Bookshelf.js 建立在此之上,提供了創建數據模型、形成這些模型之間的關係以及查詢數據庫時所需的其他常見任務的功能。
Bookshelf 還支持多個數據庫後端,如 MySQL、PostgreSQL 和 SQLite。這樣,您可以在需要時輕鬆切換數據庫,或者在開發期間使用 SQLite 等較小的數據庫,在生產中使用 Postgre。
在整篇文章中,我將向您展示如何充分利用 Node ORM,包括連接到數據庫、創建模型以及保存/加載對象。
安裝書架
Bookshelf 與大多數 Node 包有點不同,它不會自動為您安裝所有依賴項。在這種情況下,您必須手動安裝 Knex 和 Bookshelf:
$ npm install knex --save
$ npm install bookshelf --save
除此之外,您還需要選擇要使用 Bookshelf 的數據庫。您的選擇是:
- pg (PostgreSQL)
- mysql
- mariasql
- sqlite3
這些可以安裝:
$ npm install pg --save
$ npm install mysql --save
$ npm install mariasql --save
$ npm install sqlite3 --save
我傾向於對我的項目做的一件事是使用 --save
安裝生產級數據庫(如 Postgre) , 同時使用 --save-dev
用於開發期間使用的較小的數據庫(如 SQLite)。
$ npm install pg --save
$ npm install sqlite3 --save-dev
通過這種方式,我們可以輕鬆地在生產和開發中的數據庫之間切換,而不必擔心不必要的依賴項會淹沒我的生產環境。
連接數據庫
所有底層函數,比如連接到數據庫,都由底層的 Knex 庫處理。所以,很自然,為了初始化你的 bookshelf
例如,您需要創建一個 knex
首先實例化,像這樣:
var knex = require('knex')({
client: 'sqlite3',
connection: {
filename: './db.sqlite'
}
});
var bookshelf = require('bookshelf')(knex);
現在您可以使用 bookshelf
創建模型的實例。
設置表格
正如其自己的網站所述,Knex 是一個“包含電池”的 SQL 查詢構建器,因此您可以通過 Knex 執行您想要對原始 SQL 語句執行的任何操作。這些重要功能之一是表的創建和操作。 Knex 可直接用於在數據庫中設置您的架構(想想數據庫初始化、架構遷移等)。
因此,首先,您需要使用 knex.schema.createTable()
創建表 , 這將創建並返回一個表對象,其中包含一堆架構構建函數,例如 table.increments()
, table.string()
, 和 table.date()
.對於您創建的每個模型,您都需要為每個模型執行以下操作:
knex.schema.createTable('users', function(table) {
table.increments();
table.string('name');
table.string('email', 128);
table.string('role').defaultTo('admin');
table.string('password');
table.timestamps();
});
在這裡您可以看到我們創建了一個名為“users”的表,然後我們使用列“name”、“email”、“role”和“password”對其進行初始化。我們甚至可以更進一步,指定字符串列的最大長度('email' 列為 128)或默認值('role' 列為'admin')。
還提供了一些方便的功能,例如 timestamps()
.此函數將向表中添加兩個時間戳列,created_at
和 updated_at
.如果您使用它,請考慮同時設置 hasTimestamps
true
的屬性 在您的模型中(請參閱下面的“創建模型”)。
您可以為每個表/列指定更多選項,因此我絕對建議您查看完整的 Knex 文檔以了解更多詳細信息。
創建模型
我對 Bookshelf 的抱怨之一是你總是需要一個初始化的 bookshelf
實例以創建模型,因此如果將所有模型保存在不同的文件中,構建某些應用程序可能會有點混亂。就個人而言,我更喜歡只製作 bookshelf
使用 global.bookshelf = bookshelf
的全局變量 ,但這不一定是最好的方法。
無論如何,讓我們看看創建一個簡單的模型需要什麼:
var User = bookshelf.Model.extend({
tableName: 'users',
hasTimestamps: true,
verifyPassword: function(password) {
return this.get('password') === password;
}
}, {
byEmail: function(email) {
return this.forge().query({where:{ email: email }}).fetch();
}
});
在這裡,我們有一個非常簡單的模型來演示一些可用的功能。首先,只有 必需的屬性是 tableName
,它告訴模型在數據庫中的何處保存和加載數據。顯然,建立模型非常簡單,因為所有模式聲明都已經在其他地方完成了。
至於其餘的屬性/功能,這裡是 User
的簡要說明 包括:
tableName
:一個字符串,告訴模型在數據庫中的何處保存和加載數據(必需)hasTimestamps
:一個布爾值,告訴模型我們是否需要created_at
和updated_at
時間戳verifyPassword
:一個實例函數byEmail
:類(靜態)函數
因此,例如,我們將使用 byEmail
作為通過電子郵件地址查詢用戶的更快捷方式:
User.byEmail('admin@javascript-js.com').then(function(u) {
console.log('Got user:', u.get('name'));
});
注意您如何訪問 Bookshelf 中的模型數據。而不是使用直接屬性(如 u.name
),我們必須使用 .get()
方法。
ES6 支持
截至撰寫本文時,Bookshelf 似乎還沒有完整的 ES6 支持(請參閱此問題)。但是,您仍然可以使用新的 ES6 類編寫大部分模型代碼。使用上面的模型,我們可以使用新的 class
重新創建它 語法如下:
class User extends bookshelf.Model {
get tableName() {
return 'users';
}
get hasTimestamps() {
return true;
}
verifyPassword(password) {
return this.get('password') === password;
}
static byEmail(email) {
return this.forge().query({where:{ email: email }}).fetch();
}
}
現在這個模型可以完全像以前一樣使用。這種方法不會給你帶來任何功能上的優勢,但它對某些人來說更熟悉,所以如果你想利用它。
收藏
在 Bookshelf 中,您還需要為給定模型的集合創建一個單獨的對象。所以如果你想對多個 User
執行一個操作 s 同時,比如需要創建一個Collection
.
繼續上面的示例,下面是我們如何創建 Users
Collection
對象:
var Users = bookshelf.Collection.extend({
model: User
});
很簡單,對吧?現在我們可以輕鬆地查詢所有用戶(儘管使用 .fetchAll()
的模型已經可以做到這一點 ):
免費電子書:Git Essentials
查看我們的 Git 學習實踐指南,其中包含最佳實踐、行業認可的標準以及隨附的備忘單。停止谷歌搜索 Git 命令並真正學習 它!
Users.forge().fetch().then(function(users) {
console.log('Got a bunch of users!');
});
更好的是,我們現在可以在整個集合上使用一些不錯的模型方法,而不必單獨迭代每個模型。其中一種似乎得到大量使用的方法,尤其是在 web 應用程序中,是 .toJSON()
:
exports.get = function(req, res) {
Users.forge().fetch().then(function(users) {
res.json(users.toJSON());
});
};
這將返回整個集合的純 JavaScript 對象。
擴展你的模型
作為一名開發人員,我遵循的最重要的原則之一是 DRY(不要重複自己)原則。這只是模型/模式擴展對您的軟件設計如此重要的眾多原因之一。
使用 Bookshelf 的 .extend()
方法,您可以繼承基礎模型的所有屬性、實例方法和類方法。通過這種方式,您可以創建並利用尚未提供的基本方法,例如 .find()
, .findOne()
等。
模型擴展的一個很好的例子是 bookshelf-modelbase 項目,它提供了許多您期望在大多數 ORM 中成為標準的缺失方法。
如果您要創建自己的簡單基礎模型,它可能如下所示:
var model = bookshelf.Model.extend({
hasTimestamps: ['created_at', 'updated_at'],
}, {
findAll: function(filter, options) {
return this.forge().where(filter).fetchAll(options);
},
findOne: function(query, options) {
return this.forge(query).fetch(options);
},
create: function(data, options) {
return this.forge(data).save(null, options);
},
});
現在您的所有模型都可以利用這些有用的方法。
保存和更新模型
有幾種不同的方法可以將模型保存在 Bookshelf 中,具體取決於您的偏好和數據格式。
第一種也是最明顯的方法是調用 .save()
在模型實例上。
var user = new User();
user.set('name', 'Joe');
user.set('email', 'admin@javascript-js.com');
user.set('age', 28);
user.save().then(function(u) {
console.log('User saved:', u.get('name'));
});
這適用於您自己創建的模型(如上面的模型),或從查詢調用返回給您的模型實例。
另一種選擇是使用 .forge()
方法並用數據初始化它。 'Forge' 實際上只是創建新模型的一種簡寫方式(例如 new User()
)。但是這樣你在開始查詢/保存字符串之前不需要額外的行來創建模型。
使用 .forge()
,上面的代碼應該是這樣的:
var data = {
name: 'Joe',
email: 'admin@javascript-js.com',
age: 28
}
User.forge(data).save().then(function(u) {
console.log('User saved:', u.get('name'));
});
這不會真正為您節省任何代碼行,但如果 data
會很方便 實際上是傳入的 JSON 或類似的東西。
加載模型
下面我就講講如何用Bookshelf從數據庫中加載模型。
而 .forge()
在保存文檔方面並沒有真正幫助我們太多,它確實有助於加載它們。創建一個空模型實例只是為了從數據庫中加載數據會有點尷尬,所以我們使用 .forge()
而是。
最簡單的加載示例是使用 .fetch()
獲取單個模型 :
User.forge({email: 'admin@javascript-js.com'}).fetch().then(function(user) {
console.log('Got user:', user.get('name'));
});
我們在這裡所做的只是獲取與給定查詢匹配的單個模型。可以想像,查詢可以隨心所欲地複雜(例如在 name
上進行約束 和 age
列)。
就像在普通的舊 SQL 中一樣,您可以極大地自定義查詢和返回的數據。例如,這個查詢只會給我們驗證用戶所需的數據:
var email = '...';
var plainTextPassword = '...';
User.forge({email: email}).fetch({columns: ['email', 'password_hash', 'salt']})
.then(function(user) {
if (user.verifyPassword(plainTextPassword)) {
console.log('User logged in!');
} else {
console.log('Authentication failed...');
}
});
更進一步,我們可以使用 withRelations
自動加載相關模型的選項,我們將在下一節中看到。
模型關係
在許多應用程序中,您的模型需要引用其他模型,這是在 SQL 中使用外鍵實現的。 Bookshelf 通過關係支持一個簡單的版本。
在您的模型中,您可以準確地告訴 Bookshelf 其他模型是如何相互關聯的。這是使用 belongsTo()
實現的 , hasMany()
, 和 hasOne()
(除其他外)方法。
因此,假設您有兩個模型,用戶和地址。用戶可以有多個地址(一個用於發貨,一個用於結算等),但一個地址只能屬於一個用戶。鑑於此,我們可以這樣設置模型:
var User = bookshelf.Model.extend({
tableName: 'users',
addresses: function() {
return this.hasMany('Address', 'user_id');
},
});
var Address = bookshelf.Model.extend({
tableName: 'addresses',
user: function() {
return this.belongsTo('User', 'user_id');
},
});
請注意,我在這裡使用註冊表插件,它允許我使用字符串引用地址模型。
hasMany()
和 belongsTo()
方法告訴書架每個模型是如何相互關聯的。用戶“有很多”地址,而地址“屬於”單個用戶。第二個參數是表示模型鍵位置的列名。在這種情況下,兩者 模型引用 user_id
地址表中的列。
現在我們可以通過使用 withRelated
來利用這種關係 .fetch()
上的選項 方法。所以如果我想加載一個用戶和 一個電話就能知道他們所有的地址,我可以這樣做:
User.forge({email: 'admin@javascript-js.com'}).fetch({withRelated: ['addresses']})
.then(function(user) {
console.log('Got user:', user.get('name'));
console.log('Got addresses:', user.related('addresses'));
});
如果我們要獲取用戶模型沒有 withRelated
選項然後 user.related('addresses')
只會返回一個空的 Collection 對象。
一定要利用這些關係方法,它們比創建自己的 SQL JOIN 更容易使用:)
好人
Bookshelf 是那些似乎試圖而不是變得過於臃腫並且只堅持核心功能的圖書館之一。這很棒,因為 是 的功能 那里工作得很好。
Bookshelf 還有一個不錯的、強大的 API,讓您可以輕鬆地在它之上構建您的應用程序。因此,您不必糾結於那些對如何使用它們做出糟糕假設的高級方法。
壞人
雖然我確實認為 Bookshelf/Knex 為您提供了一些較低級別的功能很好,但我仍然認為還有改進的空間。例如,所有表/模式設置都由您決定,並且沒有一種簡單的方法可以在模型中指定您的模式(如在普通 JS 對像中)。表/模式設置必須在 API 調用中指定,這並不是那麼容易閱讀和調試。
我的另一個抱怨是他們遺漏了許多應該與基本模型一起標準的輔助方法,例如 .create()
, .findOne()
, .upsert()
, 和數據驗證。這正是我提到 bookshelf-modelbase
的原因 項目更早,因為它填補了許多這些空白。
結論
總的來說,我已經非常喜歡使用 Bookshelf/Knex 進行 SQL 工作,儘管我確實認為我剛才提到的一些問題可能會讓許多習慣使用 ORM 的開發人員望而卻步,這些 ORM 幾乎可以為他們開箱即用。另一方面,對於喜歡擁有大量控制權的其他開發人員來說,這是一個完美的庫。
雖然我試圖在本文中涵蓋盡可能多的核心 API,但仍有很多功能我沒有涉及,因此請務必查看項目文檔以獲取更多信息。
您是否使用過 Bookshelf.js 或 Knex.js?你怎麼看?請在評論中告訴我們!