Технология Node все чаще используется в сочетании с инструментами командной строки и настольными приложениями на базе Electron, поэтому в книгу были включены главы, посвященные обеим областям. Внутри поста будет рассмотрен отрывок «Хранение данных в приложениях»
Node.js ориентируется на невероятно широкий спектр разработчиков со столь же разнообразными потребностями. Ни одна база данных или технология хранения данных не способна удовлетворить большинство сценариев использования, обслуживаемых Node. В этой главе предоставлен широкий обзор возможностей хранения данных наряду с некоторыми важными высокоуровневыми концепциями и терминологией.
8.1. Реляционные базы данных
На протяжении почти всего времени существования веб-технологий реляционные базы данных занимали ведущее место в области хранения данных. Эта тема уже рассматривается во многих других книгах и образовательных программах, поэтому мы не будем тратить много времени на ее рассмотрение.
Реляционные базы данных, базирующиеся на математических концепциях реляционной алгебры и теории множеств, известны с 1970-х годов. Схема (schema) определяет формат различных типов данных и отношения, существующие между этими типами. Например, при построении социальной сети можно создать типы данных User и Post и определить отношения «один ко многим» между User и Post. Далее на языке SQL (Structured Query Language) формулируются запросы к данным типа «Получить все сообщения, принадлежащие пользователю с идентификатором 123», или на SQL: SELECT * FROM post WHERE user_id=123.
8.2. PostgreSQL
MySQL и PostgreSQL (Postgres) остаются самыми популярными реляционными базами данных для приложений Node. Различия между реляционными базами данных в основном эстетические, поэтому этот раздел в равной степени относится и к другим реляционным базам данных — например, MySQL в Node. Но сначала разберемся, как установить Postgres на машине разработки.
8.2.1. Установка и настройка
Сначала нужно установить Postgres в вашей системе. Простой команды npm install для этого недостаточно. Инструкции по установке зависят от платформы. В macOS установка сводится к простой последовательности команд:
brew update
brew install postgres
Если в вашей системе поддержка Postgres уже установлена, вы можете столкнуться с проблемами при попытке обновления. Выполните инструкции для своей платформы, чтобы произвести миграцию существующих баз данных, либо полностью сотрите каталог базы данных:
# WARNING: will delete existing postgres configuration & data
rm –rf /usr/local/var/postgres
Затем инициализируйте и запустите Postgres:
initdb -D /usr/local/var/postgres
pg_ctl -D /usr/local/var/postgres -l logfile start
Эти команды запускают демона Postgres. Демон должен запускаться каждый раз, когда вы перезагружаете компьютер. Возможно, вам строит настроить автоматическую загрузку демона Postgres при запуске; во многих сетевых руководствах можно найти описание этого процесса для вашей операционной системы.
В составе Postgres устанавливаются некоторые административные программы командной строки. Ознакомьтесь с ними; необходимую информацию можно найти в электронной документации.
8.2.2. Создание базы данных
После того как демон Postgres заработает, необходимо создать базу данных. Эту процедуру достаточно выполнить всего один раз. Проще всего воспользоваться программой createdb из режима командной строки. Следующая команда создает базу данных с именем articles:
createdb articles
Если операция завершается успешно, команда ничего не выводит. Если база данных с указанным именем уже существует, команда ничего не делает и сообщает об ошибке.
Как правило, приложения в любой момент времени подключены только к одной базе данных, хотя можно настроить сразу несколько баз данных в зависимости от режима, в котором работает база данных. Во многих приложениях различаются по крайней мере два режима: режим разработки и режим реальной эксплуатации.
Чтобы удалить все данные из существующей базы данных, выполните команду dropdb с терминала, передав ей имя базы данных в аргументе:
dropdb articles
Чтобы снова использовать базу данных, необходимо выполнить команду createdb.
8.2.3. Подключение к Postgres из Node
Самый популярный пакет для взаимодействия с Postgres из Node называется pg. Его можно установить при помощи npm:
npm install pg --save
Когда сервер Postgres заработает, база данных будет создана, а пакет pg установлен, вы сможете переходить к использованию базы данных из Node. Прежде чем вводить какие-либо команды к серверу, необходимо создать подключение к нему, как показано в листинге 8.1
const pg = require('pg');
const db = new pg.Client({ database: 'articles' }); < Параметры конфигурации подключения.
db.connect((err, client) => {
if (err) throw err;
console.log('Connected to database', db.database);
db.end(); < Закрывает подключение к базе данных, позволяя процессу node завершиться.
});
Подробную документацию по pg.Client и другим методам можно найти на вики-странице пакета pg на GitHub.
8.2.4. Определение таблиц
Чтобы хранить данные в PostgreSQL, сначала необходимо определить таблицы и формат данных, которые в них будут храниться. Пример такого рода приведен в листинге 8.2 (ch08-databases/listing8_3 в архиве исходного кода книги).
Листинг 8.2. Определение схемы
db.query(`
CREATE TABLE IF NOT EXISTS snippets (
id SERIAL,
PRIMARY KEY(id),
body text
);
`, (err, result) => {
if (err) throw err;
console.log('Created table "snippets"');
db.end();
});
8.2.5. Вставка данных
После того как таблица будет определена, в нее можно вставить данные запросами INSERT (листинг 8.3). Если значение id не указано, то PostgreSQL выберет его за вас. Чтобы узнать, какой идентификатор был выбран для конкретной записи, присоедините условие RETURNING id к запросу; идентификатор будет выведен в строках результата, переданного функции обратного вызова.
Листинг 8.3. Вставка данных
const body = 'hello world';
db.query(`
INSERT INTO snippets (body) VALUES (
'${body}'
)
RETURNING id
`, (err, result) => {
if (err) throw err;
const id = result.rows[0].id;
console.log('Inserted row with id %s', id);
db.query(`
INSERT INTO snippets (body) VALUES (
'${body}'
)
RETURNING id
`, () => {
if (err) throw err;
const id = result.rows[0].id;
console.log('Inserted row with id %s', id);
});
});
8.2.6. Обновление данных
После того как данные будут вставлены, их можно будет обновить запросом UPDATE (листинг 8.4). Количество записей, задействованных в обновлении, будет доступно в свойстве rowCount результата запроса. Полный пример для этого листинга содержится в каталоге ch08-databases/listing8_4.
Листинг 8.4. Обновление данных
const id = 1;
const body = 'greetings, world';
db.query(`
UPDATE snippets SET (body) = (
'${body}'
) WHERE id=${id};
`, (err, result) => {
if (err) throw err;
console.log('Updated %s rows.', result.rowCount);
});
8.2.7. Запросы на выборку данных
Одна из самых замечательных особенностей реляционных баз данных — возможность выполнения сложных произвольных запросов к данным. Запросы выполняются командой SELECT, а простейший пример такого рода представлен в листинге 8.5.
Листинг 8.5. Запрос данных
db.query(`
SELECT * FROM snippets ORDER BY id
`, (err, result) => {
if (err) throw err;
console.log(result.rows);
});
8.3. Knex
Многие разработчики предпочитают работать с командами SQL в своих приложениях не напрямую, а через абстрактную надстройку. Это желание вполне понятно: конкатенация строк в команды SQL может быть громоздким процессом, который усложняет понимание и сопровождение запросов. Сказанное особенно справедливо по отношению к языку JavaScript, в котором не было синтаксиса представления многострочных строк до появления в ES2015 шаблонных литералов (см. https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Template_literals). На рис. 8.1 показана статистика Knex с количеством загрузок, доказывающих популярность.
Knex — пакет Node, реализующий облегченную абстракцию для SQL, известную как построитель запросов. Построитель запросов формирует строки SQL через декларативный API, который имеет много общего с генерируемыми командами SQL. Knex API интуитивен и предсказуем:
knex({ client: 'mysql' })
.select()
.from('users')
.where({ id: '123' })
.toSQL();
Этот вызов создает параметризованный запрос SQL на диалекте MySQL:
select * from `users` where `id` = ?
8.3.1. jQuery для баз данных
Хотя стандарты ANSI и ISO SQL появились еще в середине 1980-х годов, большинство баз данных продолжает использовать собственные диалекты SQL. PostgreSQL является заметным исключением: эта база данных может похвастать соблюдением стандарта SQL:2008. Построитель запросов способен нормализировать различия между диалектами SQL, предоставляя единый унифицированный интерфейс для генерирования SQL в разных технологиях. Такой подход обладает очевидными преимуществами для групп, регулярно переключающихся между разными технологиями баз данных.
В настоящее время Knex.js поддерживает следующие базы данных: PostgreSQL; MSSQL; MySQL; MariaDB; SQLite3; Oracle.
В табл. 8.1 сравниваются способы генерирования команды INSERT в зависимости от выбранной базы данных.
Таблица 8.1. Сравнение команд SQL, сгенерированных Knex, для разных баз данных
Knex поддерживает обещания (promises) и обратные вызовы в стиле Node.
8.3.2. Подключение и выполнение запросов в Knex
В отличие от многих других построителей запросов, Knex также может подключаться и выполнять запросы к выбранному драйверу базы данных за вас.
db('articles')
.select('title')
.where({ title: 'Today's News' })
.then(articles => {
console.log(articles);
});
По умолчанию запросы Knex возвращают обещания, но они также поддерживают соглашения обратного вызова Node с использованием .asCallback:
db('articles')
.select('title')
.where({ title: 'Today's News' })
.asCallback((err, articles) => {
if (err) throw err;
console.log(articles);
});
В главе 3 мы взаимодействовали с базой данных SQLite непосредственно при помощи пакета sqlite3. Этот API можно переписать с использованием Knex. Прежде чем запускать этот пример, сначала проверьте из npm, что пакеты knex и sqlite3 установлены:
npm install knex@~0.12.0 sqlite3@~3.1.0 --save
В листинге 8.6 sqlite используется для реализации простой модели Article. Сохраните файл под именем db.js; он будет использоваться в листинге 8.7 для взаимодействия с базой данных.
Листинг 8.6. Использование Knex для подключения и выдачи запросов к sqlite3
const knex = require('knex');
const db = knex({
client: 'sqlite3',
connection: {
filename: 'tldr.sqlite'
},
useNullAsDefault: true < Выбор этого режима по умолчанию лучше работает при смене подсистемы баз данных.
});
module.exports = () => {
return db.schema.createTableIfNotExists('articles', table => {
table.increments('id').primary(); < Определяет первичный ключ с именем «id», значение которого автоматически увеличивается при вставке.
table.string('title');
table.text('content');
});
};
module.exports.Article = {
all() {
return db('articles').orderBy('title');
},
find(id) {
return db('articles').where({ id }).first();
},
create(data) {
return db('articles').insert(data);
},
delete(id) {
return db('articles').del().where({ id });
}
};
Листинг 8.7. Взаимодействие с API на базе Knex
db().then(() => {
db.Article.create({
title: 'my article',
content: 'article content'
}).then(() => {
db.Article.all().then(articles => {
console.log(articles);
process.exit();
});
});
})
.catch(err => { throw err });
SQLite требует минимальной настройки: вам не нужно загружать демон сервера или создавать базы данных за пределами приложения. SQLite записывает все данные в один файл. Выполнив предыдущий код, вы увидите, что в текущем каталоге появился файл articles.sqlite. Чтобы уничтожить базу данных SQLite, достаточно удалить всего один файл:
rm articles.sqlite
SQLite также поддерживает режим работы в памяти, при котором запись на диск вообще не осуществляется. Этот режим обычно используется для ускорения выполнения автоматизированных тестов. Для настройки режима работы в памяти используется специальное имя файла :memory:. При открытии нескольких подключений к файлу :memory: каждое подключение получает собственную изолированную базу данных:
const db = knex({
client: 'sqlite3',
connection: {
filename: ':memory:'
},
useNullAsDefault: true
});
» Более подробно с книгой можно ознакомиться на сайте издательства
» Оглавление
» Отрывок
Для Хаброжителей скидка 20% по купону — Node.js
Комментарии (22)
jehy
20.03.2018 18:08db.query(`INSERT INTO snippets (body) VALUES ( '${body}' )RETURNING id`);
За такие примеры книгу нужно сжечь перед прочтением. Понятно, что это простейший тестовый запрос, но ни словом упомянуто о том, почему так делать нельзя — отсюда и берутся в 2018 году разработчики, которые не знают, что такоеЗаголовок спойлераsql injection.megafax
21.03.2018 08:44Как минимум плохим тоном является указание объектов базы данный без нужных кавычек ("" или `` или []). QueryBuilder нужен для упрощения задачи, но если не понимать что он делает, хотя бы азы, то смысла от его применения будет немного.
barkalov
21.03.2018 14:06Проблема — потенциальная инъекция. Кавычки — это мелочь.
megafax
21.03.2018 14:37Ой ли, кавычки, например, в PostgreSQL никакая не мелочь
barkalov
21.03.2018 14:47Вы понимаете что такое SQL-инъекция? DROP DATABASE — это ещё не самый страшный вариант. А что кавычки? Подумаешь, запрос ошибку вернёт…
megafax
21.03.2018 15:10Я понимаю что такое SQL-инъекция и как с ее помощью можно существенно «нагадить». И собственно DROP DATABASE, как Вы сказали, не самое страшное. Есть еще много способов, что помощи них и неправильно выставленных прав доступа можно натворить. Мое мнение — это правильное экранирование, которое и спасет собственно от инъекции. Что и имелось в виду, при ответе, что кавычки как минимум важны.
barkalov
21.03.2018 15:19И как кавычки помогут заэкранировать? Не понимаю…
megafax
21.03.2018 15:47Пример 1:
WHERE «id» = 10 и WHERE «id» = '10'::integer
Пример 2:
WHERE `id`='asdasdasd\'; DROP… --'barkalov
22.03.2018 06:16Поясните, что именно вы имели ввиду, пожалуйста?
megafax
22.03.2018 15:48Ну вот Вы какие методы устранения уязвимостей на SQL-inject знаете?
barkalov
22.03.2018 15:57Prepared Statements и экранирование.
Я в упор не понимаю, как этому мешает или помогает ваше «указание объектов базы данный без нужных кавычек».megafax
22.03.2018 16:18Как минимум плохим тоном
Вы этот момент упустили
Что касается ветки «спора», то мы с Вами о разном спорилиbarkalov
22.03.2018 20:45Мое мнение — это правильное экранирование, которое и спасет собственно от инъекции. Что и имелось в виду, при ответе, что кавычки как минимум важны.
И как кавычки помогут заэкранировать? Не понимаю…Как минимум плохим тоном
джекичан.jpg
akadex
21.03.2018 08:44Книга — это всегда здорово. Куплю и почитаю. 15 лет уже не читал на бумаге ничего кроме тех. литературы. Но вот это стоит! Потом повешу в рамку. )
tehSLy
21.03.2018 10:20-1КМК, если в одном предложении слова «Книга» и «JS», то здесь что-то не так. Книга по JS устаревает еще до ее выхода с учетом адской нестабильности JS экосистемы, всевозможных фреймворков и технологий. (А про фронтэнд даже читать смешно, там статьи в интернете то не успевают за технологиями, а вы — книги)
Zenitchik
21.03.2018 14:09Я тоже сперва так подумал, а потом сообразил: в силу специализации часть технологий могла пройти мимо меня, а книга — позволяет быть в курсе того, что я пропустил, без существенных трудозатрат с моей стороны.
tehSLy
21.03.2018 14:40Зачем быть в курсе устаревших технологий? Все что сейчас используется в стеке так, или иначе есть на просторах сети в весьма удобном виде, от видео до интерактивных обучалок. Тащить старое в продакшн довольно непонятное занятие, да и если они уже там, то и книга не нужна, Вы с ними, скорее всего итак знакомы. Книги логичнее писать про фундаментальные вещи, алгоритмы, паттерны(и те в мире JS склонны к весьма быстрым изменениям), а не про то, что сегодня/завтра может нещадно устареть. Разбор движка был бы более в тему, но никак не гайд по фреймворкам на JS. Такую книгу завтра откроешь, обнаружишь, что 9/10 пакетов обозреваемых книгой уже на 2-3 релиза впереди, API поменялось с breaking change, а из оставшихся пакетов новые требуют новых версий зависимостей, что опять же ведет к невозможности работать. Я люблю книги, но эта из разряда — «Как провести сегодняшний день (в 2х томах)».
Zenitchik
21.03.2018 15:33Зачем быть в курсе устаревших технологий?
Для общего развития. Вам не случалось читать книги по BASIC 80-х годов издания? Я читал с удовольствием.
Кроме того, они могли и не устареть. Технологии устаревают с неодинаковой скоростью.
myemuk
22.03.2018 08:35+1Технологии хоть и устаревают, но в данном случае эволюцию никто не отменял. И знание «старых» технологий позволяет понимать преимущество новых, причину, по которой стоит пользоваться новым.
Для примера могу привести конкуренцию Apache и Nginx. В процессе эксплуатации технологии проявляются проблемы, решения которых внедряются в новый продукт.
Без истории невозможно понять ценность того, что имеешь и чем пользуешься.
timfcsm
21.03.2018 15:30Напомните, пожалуйста, что кардинально изменилось в экосистеме nodejs за последние пару лет… даже большинство новых фич языка было реализовано уже в 6 версии ноды, последний мажорный релиз того-же экспресс.жс был около трех лет назад, разве что коа2 из альфы вышла
sw0rl0k
Наверняка кто-то задаст этот вопрос(и странно, что издатели не пишут это сразу на обложке): версия ноды в книге 6.5.0 (по крайней мере в английской версии второго издания).
ph_piter Автор
Версия от 6 и выше.