Одним из наиболее распространенных ресурсов, с которыми вы будете взаимодействовать на таком языке, как Node.js (в основном это веб-язык), являются базы данных. А поскольку SQL является наиболее распространенным из всех различных типов, вам понадобится хорошая библиотека, которая поможет вам взаимодействовать с ним и его многочисленными функциями.
Bookshelf.js - один из самых популярных ORM-пакетов Node.js. Он основан на Knex.js , гибком конструкторе запросов, который работает с PostgreSQL, MySQL и SQLite3. Bookshelf.js опирается на это, предоставляя функциональные возможности для создания моделей данных, формирования отношений между этими моделями и других общих задач, необходимых при запросе базы данных.
Книжная полка также поддерживает несколько серверных баз данных, таких как MySQL , PostgreSQL и SQLite . Таким образом, вы можете легко переключать базы данных при необходимости или использовать меньшую базу данных, такую как SQLite во время разработки и Postgre в производстве.
В этой статье я покажу вам, как получить максимальную отдачу от этой Node ORM, включая подключение к базе данных, создание моделей и сохранение / загрузку объектов.
Установить книжную полку
Книжная полка немного отличается от большинства пакетов Node тем, что не устанавливает все свои зависимости автоматически. В этом случае вы должны вручную установить Knex вместе с Книжной полкой:
$ npm install knex --save
$ npm install bookshelf --save
В дополнение к этому вам нужно выбрать, с какой базой данных вы хотите использовать Книжную полку. Ваш выбор:
Их можно установить с помощью:
$ npm install pg --save
$ npm install mysql --save
$ npm install mariasql --save
$ npm install sqlite3 --save
Одна вещь, которую я обычно делаю со своими проектами, - это установка
БД производственного уровня (например, Postgre) с использованием
--save
, при использовании --save-dev
для меньшей БД, такой как
SQLite, для использования во время разработки.
$ npm install pg --save
$ npm install sqlite3 --save-dev
Таким образом, мы можем легко переключаться между базами данных в производстве и разработке, не беспокоясь о переполнении моей производственной среды ненужными зависимостями.
Подключение к базе данных
Все функции нижнего уровня, такие как подключение к базе данных,
обрабатываются базовой библиотекой Knex. Итак, естественно, чтобы
инициализировать bookshelf
вам нужно сначала knex
экземпляр 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();
});
Здесь вы можете видеть, что мы создаем таблицу под названием «пользователи», которую затем инициализируем столбцами «имя», «электронная почта», «роль» и «пароль». Мы даже можем пойти дальше и указать максимальную длину строкового столбца (128 для столбца «электронная почта») или значение по умолчанию («admin» для столбца «роль»).
Также предусмотрены некоторые удобные функции, например timestamps()
.
Эта функция добавит в таблицу два столбца временных меток created_at
и
updated_at
. Если вы используете это, подумайте также о
hasTimestamps
чтобы true
в вашей модели (см. «Создание модели»
ниже).
Вы можете указать еще несколько параметров для каждой таблицы / столбца, поэтому я определенно рекомендую ознакомиться с полной документацией Knex для получения более подробной информации.
Создание модели
Одна из моих проблем с Книжной полкой заключается в том, что вам всегда
нужен инициализированный 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(' [email protected] ').then(function(u) {
console.log('Got user:', u.get('name'));
});
Обратите внимание, как вы получаете доступ к данным модели на книжной
полке. Вместо использования прямого свойства (например, 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();
}
}
И теперь эту модель можно использовать точно так же, как и раньше. Этот метод не даст вам никаких функциональных преимуществ, но некоторым он более знаком, поэтому воспользуйтесь им, если хотите.
Коллекции
В Книжной полке также необходимо создать отдельный объект для коллекций
данной модели. Поэтому, если вы хотите выполнить операцию одновременно с
несколькими User
, например, вам необходимо создать Collection
.
Продолжая наш пример сверху, вот как мы создадим объект Users
Collection
var Users = bookshelf.Collection.extend({
model: User
});
Довольно просто, правда? Теперь мы можем легко запросить всех
пользователей с помощью (хотя это уже было возможно с моделью,
использующей .fetchAll()
):
Users.forge().fetch().then(function(users) {
console.log('Got a bunch of users!');
});
Более того, теперь мы можем использовать несколько хороших методов
модели для коллекции в целом, вместо того, чтобы перебирать каждую
модель по отдельности. Один из этих методов, который, кажется, находит
.toJSON()
:
exports.get = function(req, res) {
Users.forge().fetch().then(function(users) {
res.json(users.toJSON());
});
};
Это возвращает простой объект JavaScript всей коллекции.
Расширение ваших моделей
Как разработчик, я придерживаюсь одного из важнейших принципов - DRY (Don't Repeat Yourself). Это лишь одна из многих причин, почему расширение модели / схемы так важно для разработки вашего программного обеспечения.
Используя .extend()
, вы можете унаследовать все свойства, методы
экземпляра и методы класса базовой модели. Таким образом, вы можете
создавать и использовать базовые методы, которые еще не предоставлены,
например .find()
, .findOne()
и т. Д.
Отличным примером расширения модели является проект bookhelf-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);
},
});
Теперь все ваши модели могут воспользоваться этими полезными методами.
Сохранение и обновление моделей
Есть несколько разных способов сохранить модели на книжной полке, в зависимости от ваших предпочтений и формата ваших данных.
Первый и наиболее очевидный способ - просто вызвать .save()
для
экземпляра модели.
var user = new User();
user.set('name', 'Joe');
user.set('email', ' [email protected] ');
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: ' [email protected] ',
age: 28
}
User.forge(data).save().then(function(u) {
console.log('User saved:', u.get('name'));
});
На самом деле это не спасет вам ни одной строчки кода, но может быть
удобно, если data
на самом деле представляют собой входящий JSON или
что-то в этом роде.
Загрузка моделей
Здесь я расскажу о том, как загружать модели из базы данных с помощью Книжной полки.
Хотя .forge()
самом деле не очень помог нам в сохранении документов,
он определенно помогает в их загрузке. Было бы немного неудобно
создавать пустой экземпляр модели только для загрузки данных из базы
данных, поэтому вместо этого .forge()
Самый простой пример загрузки - просто получить одну модель с помощью
.fetch()
:
User.forge({email: ' [email protected] '}).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
еще дальше, мы можем использовать параметр withRelations
для автоматической загрузки связанных моделей, что мы увидим в следующем
разделе.
Модельные отношения
Во многих приложениях ваши модели должны будут ссылаться на другие модели, что достигается в SQL с использованием внешних ключей. Простая версия этого поддерживается в Книжной полке через отношения.
В своей модели вы можете точно указать Bookshelf, как другие модели
связаны друг с другом. Это достигается с помощью belongsTo()
,
hasMany()
и hasOne()
(среди прочих).
Допустим, у вас есть две модели: User и Address. Пользователь может иметь несколько адресов (один для доставки, один для выставления счетов и т. Д.), Но адрес может принадлежать только одному пользователю. Учитывая это, мы могли бы настроить наши модели следующим образом:
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');
},
});
Обратите внимание, что здесь я использую плагин реестра , который позволяет мне ссылаться на модель Address со строкой.
hasMany()
и belongsTo()
Bookshelf, как каждая модель связана друг с
другом. У пользователя «много» адресов, а адрес «принадлежит» одному
пользователю. Второй аргумент - это имя столбца, указывающее
расположение ключа модели. В этом случае обе модели ссылаются на
user_id
в таблице адресов.
Теперь мы можем воспользоваться этой связью, используя withRelated
опции .fetch()
методу. Поэтому, если бы я хотел загрузить пользователя
и все его адреса одним вызовом, я мог бы просто сделать:
User.forge({email: ' [email protected] '}).fetch({withRelated: ['addresses']})
.then(function(user) {
console.log('Got user:', user.get('name'));
console.log('Got addresses:', user.related('addresses'));
});
Если бы мы получили модель User без опции withRelated
user.related('addresses')
просто вернул бы пустой объект Collection.
Обязательно воспользуйтесь преимуществами этих методов отношения, их гораздо проще использовать, чем создавать собственные SQL-соединения :)
Добро
Книжная полка - одна из тех библиотек, которая, кажется, старается не слишком раздуваться, а просто придерживается основных функций. Это здорово, потому что функции, которые там есть, работают очень хорошо.
Книжная полка также имеет приятный мощный API, который позволяет легко создавать на его основе свое приложение. Таким образом, вам не нужно возиться с высокоуровневыми методами, которые делают неверные предположения о том, как они будут использоваться.
Плохо
Хотя я думаю, что это хорошо, что Bookshelf / Knex предоставляет вам некоторые функции более низкого уровня, я все же думаю, что есть возможности для улучшения. Например, все настройки таблицы / схемы оставлены на ваше усмотрение, и нет простого способа указать вашу схему (например, в простом объекте JS) в модели. Настройка таблицы / схемы должна быть указана в вызовах API, что не так легко читать и отлаживать.
Еще одна моя проблема заключается в том, что они упустили из виду многие
вспомогательные методы, которые должны входить в стандартную базовую
модель, такие как .create()
, .findOne()
, .upsert()
и проверка
данных. Именно поэтому я ранее упоминал о bookshelf-modelbase
поскольку он заполняет многие из этих пробелов.
Заключение
В целом я стал большим поклонником использования Bookshelf / Knex для работы с SQL, хотя я действительно думаю, что некоторые из проблем, о которых я только что упомянул, могут отпугнуть многих разработчиков, которые привыкли использовать ORM, которые делают практически все для их прямо из коробки. С другой стороны, для других разработчиков, которым нравится иметь большой контроль, это идеальная библиотека для использования.
Хотя я попытался охватить в этой статье как можно больше основных API, есть еще немало функций, которые я не затронул, поэтому обязательно ознакомьтесь с документацией по проекту для получения дополнительной информации.
Вы использовали Bookshelf.js или Knex.js? Как вы думаете? Дайте нам знать об этом в комментариях!