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

Комментарии (22)


  1. sw0rl0k
    20.03.2018 13:26
    +1

    Наверняка кто-то задаст этот вопрос(и странно, что издатели не пишут это сразу на обложке): версия ноды в книге 6.5.0 (по крайней мере в английской версии второго издания).


    1. ph_piter Автор
      20.03.2018 14:57

      Версия от 6 и выше.


  1. jehy
    20.03.2018 18:08

    db.query(`INSERT INTO snippets (body) VALUES ( '${body}' )RETURNING id`);

    За такие примеры книгу нужно сжечь перед прочтением. Понятно, что это простейший тестовый запрос, но ни словом упомянуто о том, почему так делать нельзя — отсюда и берутся в 2018 году разработчики, которые не знают, что такое
    Заголовок спойлера
    sql injection.


    1. megafax
      21.03.2018 08:44

      Как минимум плохим тоном является указание объектов базы данный без нужных кавычек ("" или `` или []). QueryBuilder нужен для упрощения задачи, но если не понимать что он делает, хотя бы азы, то смысла от его применения будет немного.


      1. barkalov
        21.03.2018 14:06

        Проблема — потенциальная инъекция. Кавычки — это мелочь.


        1. megafax
          21.03.2018 14:37

          Ой ли, кавычки, например, в PostgreSQL никакая не мелочь


          1. barkalov
            21.03.2018 14:47

            Вы понимаете что такое SQL-инъекция? DROP DATABASE — это ещё не самый страшный вариант. А что кавычки? Подумаешь, запрос ошибку вернёт…


            1. megafax
              21.03.2018 15:10

              Я понимаю что такое SQL-инъекция и как с ее помощью можно существенно «нагадить». И собственно DROP DATABASE, как Вы сказали, не самое страшное. Есть еще много способов, что помощи них и неправильно выставленных прав доступа можно натворить. Мое мнение — это правильное экранирование, которое и спасет собственно от инъекции. Что и имелось в виду, при ответе, что кавычки как минимум важны.


              1. barkalov
                21.03.2018 15:19

                И как кавычки помогут заэкранировать? Не понимаю…


                1. megafax
                  21.03.2018 15:47

                  Пример 1:
                  WHERE «id» = 10 и WHERE «id» = '10'::integer
                  Пример 2:
                  WHERE `id`='asdasdasd\'; DROP… --'


                  1. barkalov
                    22.03.2018 06:16

                    Поясните, что именно вы имели ввиду, пожалуйста?


                    1. megafax
                      22.03.2018 15:48

                      Ну вот Вы какие методы устранения уязвимостей на SQL-inject знаете?


                      1. barkalov
                        22.03.2018 15:57

                        Prepared Statements и экранирование.
                        Я в упор не понимаю, как этому мешает или помогает ваше «указание объектов базы данный без нужных кавычек».


                        1. megafax
                          22.03.2018 16:18

                          Как минимум плохим тоном
                          Вы этот момент упустили
                          Что касается ветки «спора», то мы с Вами о разном спорили


                          1. barkalov
                            22.03.2018 20:45

                            Мое мнение — это правильное экранирование, которое и спасет собственно от инъекции. Что и имелось в виду, при ответе, что кавычки как минимум важны.
                            И как кавычки помогут заэкранировать? Не понимаю…
                            Как минимум плохим тоном
                            джекичан.jpg


  1. akadex
    21.03.2018 08:44

    Книга — это всегда здорово. Куплю и почитаю. 15 лет уже не читал на бумаге ничего кроме тех. литературы. Но вот это стоит! Потом повешу в рамку. )


  1. tehSLy
    21.03.2018 10:20
    -1

    КМК, если в одном предложении слова «Книга» и «JS», то здесь что-то не так. Книга по JS устаревает еще до ее выхода с учетом адской нестабильности JS экосистемы, всевозможных фреймворков и технологий. (А про фронтэнд даже читать смешно, там статьи в интернете то не успевают за технологиями, а вы — книги)


    1. Zenitchik
      21.03.2018 14:09

      Я тоже сперва так подумал, а потом сообразил: в силу специализации часть технологий могла пройти мимо меня, а книга — позволяет быть в курсе того, что я пропустил, без существенных трудозатрат с моей стороны.


      1. tehSLy
        21.03.2018 14:40

        Зачем быть в курсе устаревших технологий? Все что сейчас используется в стеке так, или иначе есть на просторах сети в весьма удобном виде, от видео до интерактивных обучалок. Тащить старое в продакшн довольно непонятное занятие, да и если они уже там, то и книга не нужна, Вы с ними, скорее всего итак знакомы. Книги логичнее писать про фундаментальные вещи, алгоритмы, паттерны(и те в мире JS склонны к весьма быстрым изменениям), а не про то, что сегодня/завтра может нещадно устареть. Разбор движка был бы более в тему, но никак не гайд по фреймворкам на JS. Такую книгу завтра откроешь, обнаружишь, что 9/10 пакетов обозреваемых книгой уже на 2-3 релиза впереди, API поменялось с breaking change, а из оставшихся пакетов новые требуют новых версий зависимостей, что опять же ведет к невозможности работать. Я люблю книги, но эта из разряда — «Как провести сегодняшний день (в 2х томах)».


        1. Zenitchik
          21.03.2018 15:33

          Зачем быть в курсе устаревших технологий?

          Для общего развития. Вам не случалось читать книги по BASIC 80-х годов издания? Я читал с удовольствием.
          Кроме того, они могли и не устареть. Технологии устаревают с неодинаковой скоростью.


        1. myemuk
          22.03.2018 08:35
          +1

          Технологии хоть и устаревают, но в данном случае эволюцию никто не отменял. И знание «старых» технологий позволяет понимать преимущество новых, причину, по которой стоит пользоваться новым.
          Для примера могу привести конкуренцию Apache и Nginx. В процессе эксплуатации технологии проявляются проблемы, решения которых внедряются в новый продукт.
          Без истории невозможно понять ценность того, что имеешь и чем пользуешься.


    1. timfcsm
      21.03.2018 15:30

      Напомните, пожалуйста, что кардинально изменилось в экосистеме nodejs за последние пару лет… даже большинство новых фич языка было реализовано уже в 6 версии ноды, последний мажорный релиз того-же экспресс.жс был около трех лет назад, разве что коа2 из альфы вышла