Книга «Node.js в действии. 2-е издание» +11


image Второе издание «Node.js в действии» было полностью переработано, чтобы отражать реалии, с которыми теперь сталкивается каждый Node-разработчик. Вы узнаете о системах построения интерфейса и популярных веб-фреймворках Node, а также научитесь строить веб-приложения на базе Express с нуля. Теперь вы сможете узнать не только о Node и JavaScript, но и получить всю информацию, включая системы построения фронтэнда, выбор веб-фреймворка, работу с базами данных в Node, тестирование и развертывание веб-приложений.

Технология 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 с количеством загрузок, доказывающих популярность.

image

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, для разных баз данных

image

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




К сожалению, не доступен сервер mySQL