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

    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
    Поделиться публикацией
    Похожие публикации
    Комментарии 30
    • +1
      Наверняка кто-то задаст этот вопрос(и странно, что издатели не пишут это сразу на обложке): версия ноды в книге 6.5.0 (по крайней мере в английской версии второго издания).
    • +3
      db.query(`INSERT INTO snippets (body) VALUES ( '${body}' )RETURNING id`);

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

      А ведь наглядно можно было заодно показать, как использование Query Builder вроде Knex помогает в этом случае.
      • –1
        Как минимум плохим тоном является указание объектов базы данный без нужных кавычек ("" или `` или []). QueryBuilder нужен для упрощения задачи, но если не понимать что он делает, хотя бы азы, то смысла от его применения будет немного.
        • 0
          Проблема — потенциальная инъекция. Кавычки — это мелочь.
          • 0
            Ой ли, кавычки, например, в PostgreSQL никакая не мелочь
            • 0
              Вы понимаете что такое SQL-инъекция? DROP DATABASE — это ещё не самый страшный вариант. А что кавычки? Подумаешь, запрос ошибку вернёт…
              • 0
                Я понимаю что такое SQL-инъекция и как с ее помощью можно существенно «нагадить». И собственно DROP DATABASE, как Вы сказали, не самое страшное. Есть еще много способов, что помощи них и неправильно выставленных прав доступа можно натворить. Мое мнение — это правильное экранирование, которое и спасет собственно от инъекции. Что и имелось в виду, при ответе, что кавычки как минимум важны.
                • 0
                  И как кавычки помогут заэкранировать? Не понимаю…
                  • 0
                    Пример 1:
                    WHERE «id» = 10 и WHERE «id» = '10'::integer
                    Пример 2:
                    WHERE `id`='asdasdasd\'; DROP… --'
                    • 0
                      Поясните, что именно вы имели ввиду, пожалуйста?
                      • 0
                        Ну вот Вы какие методы устранения уязвимостей на SQL-inject знаете?
                        • 0
                          Prepared Statements и экранирование.
                          Я в упор не понимаю, как этому мешает или помогает ваше «указание объектов базы данный без нужных кавычек».
                          • 0
                            Как минимум плохим тоном
                            Вы этот момент упустили
                            Что касается ветки «спора», то мы с Вами о разном спорили
                            • 0
                              Мое мнение — это правильное экранирование, которое и спасет собственно от инъекции. Что и имелось в виду, при ответе, что кавычки как минимум важны.
                              И как кавычки помогут заэкранировать? Не понимаю…
                              Как минимум плохим тоном
                              джекичан.jpg
      • –1

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

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

                Для общего развития. Вам не случалось читать книги по BASIC 80-х годов издания? Я читал с удовольствием.
                Кроме того, они могли и не устареть. Технологии устаревают с неодинаковой скоростью.
                • 0
                  А что в бейсике нефундаментального? Там Алгоритмы, какие то общие паттерны программирования, и все. Как я и сказал, такие вещи можно и нужно паковать в книги. И они будут актуальны и через 10 и через 20 лет. Сказать такое про JS сложно, просто потому, что между теми же ES кучи обратнонесовместимых фич, и даже основы программирования на JS через пару лет будут выглядеть моветоном в глазах более продвинутых коллег.
                  • 0
                    нефундаментального

                    Ну, например, методы работы с оперативкой напрямую. Операторы изменения собственного кода (важный приём программирования, когда дефицит оперативки).
                    Как Вам «удалить строки с такой-то по такую-то и загрузить вместо них содержимое файла такого-то»? Практически верный способ выстрелить себе в ногу, однако ж применяли!
                    Я уж не говорю о выполнении ассемблерных подпрограмм из оперативки: сперва побайтно её туда загоняем, а потом call <вектор начала подпрограммы>. (call здесь — оператор BASICa, в самом BASICe процедур тогда ещё не было — gosub это другое).
                    • 0
                      Методы работы с оперативкой, файловой системой как раз таки можно отнести к фундаментальщине же. Разбор фреймворков — нет. Да и стоит учитывать, что в 80-х годах особо не было вариантов для передачи и хранения информации в бытовом варианте, кроме книг то :D
                      Мне сложно сравнивать BASIC и JS, покуда с первым я знаком только понаслышке, но могу заявить, что сегодня дома отказался работать проект из-за разницы в версии ноды 9.6.1 -> 9.9.0, притом, что на работе таковая 9.8.*
                      (Обновлялся дома около месяца назад, и она уже(!) устарела и работает некорректно)
                      • 0
                        Методы работы с оперативкой, файловой системой как раз таки можно отнести к фундаментальщине же.

                        А изменение на лету собственных исходников?
                        Вы хоть раз слышали, чтобы такое применялось, ну, скажем, последние 20 лет?

                        Во фреймворках, которые мне не пригодились и никогда не пригодятся, могут быть идеи, которые стоит среверсинжинирить.
                • +1
                  Технологии хоть и устаревают, но в данном случае эволюцию никто не отменял. И знание «старых» технологий позволяет понимать преимущество новых, причину, по которой стоит пользоваться новым.
                  Для примера могу привести конкуренцию Apache и Nginx. В процессе эксплуатации технологии проявляются проблемы, решения которых внедряются в новый продукт.
                  Без истории невозможно понять ценность того, что имеешь и чем пользуешься.
                  • 0
                    Эволюцию никто не отменял, но преимущество ног перед плавниками, при ходьбе, очевидно и без книг. Если целью является рассмотрение процесса эволюции технологии, об этом и следует писать. Тут акцентируется внимание на стеке 2хлетней давности. Книга предполагается как источник знаний, не утрачивающий свою актуальность длительное время, иначе это просто пустой расход бумаги.
                    В книге версия ноды отличается уже на 3 релиза, и это на момент выхода. На дворе в спецификации уже стейджится es2018, в книге es2015. Плагины по бабелю и все что с этим связано меняются чуть ли не каждые полгода стабильно, перетекая от вендора к вендору. И это только по оглавлению и кускам текстов. Будет ли эта книга актуальна хотя бы к концу 2018? Я очень сомневаюсь.
                    • 0
                      у нас в конторе до сих пор приложение крутится которое только на IE заточено и в новых еджах не работает. Это конечно не javascript, но есть тонны проектов, которые на новые рельсы переводить никто не будет и которые нужно поддерживать. Поэтому знать последний стэк недостаточно, нужно уметь еще в предыдущих версиях разбираться.
                      Встречал одного шарпера, которому на тестировании дали проект ASP.net4, хотя он работал на ASP.net5. Столько нытья было по поводу устарелости, то что это нельзя поддерживать, что он не говнокодер и т.п. Одного он не понимает — заказчику не нужен идеальный на последних технологиях основанный продукт, ему нужен релиз продукта вписывающегося в дедлайны и содержащий фунциональные возможности которые клиент запросил. Унаследованный код тоже определяет — с каким стэком придется работать, хотя бы какое-то время до переноса всего добра на новый подход.
                      • 0
                        Знакомая ситуация. Но тут мне опять же сложно ответить, ибо я не знаю, насколько инертен мир шарпистов, я там мимокрокодил, только примитивщину писал.
                        По миру JS могу сказать, что если в проекте есть легаси, это надо искоренять(сейчас активно занимаемся этим в нашей компании). Через полгода-год Вы уже не найдете банально адекватного разраба на такое, и будете либо доплачивать за легаси, либо работать с джунами, которым лишь бы опыт получить.
                        Проект и не должен быть написан на bleeding edge технологиях, но тянуть в него легаси просто потому что оно там уже было — неправильно, это только свалит проект в еще больший застой, и ни к чему хорошему не приведет.
                        Соответственно, писать/читать книги по таковым, для меня сомнительное занятие. Я не против такой информации, но опять, повторюсь же, в книжном печатном формате это бессмысленно. Цифровые дайджесты, серии статей на ресурсах — да, но книги — нет.
              • +1
                Напомните, пожалуйста, что кардинально изменилось в экосистеме nodejs за последние пару лет… даже большинство новых фич языка было реализовано уже в 6 версии ноды, последний мажорный релиз того-же экспресс.жс был около трех лет назад, разве что коа2 из альфы вышла
                • 0
                  Это конечно так, но вот релизнутся в этом году декораторы и пайплайн операторы, приватные методы/поля классов, и станут новым стандартом, а в книге про них ничего. Однако они в пропосалах уже больше года висят, и ими свободно пользуются через полифилы/трайнспайлеры того же бабеля. И прочитает человек в середине 2018 такую книгу, увидит запись:
                  let string = 'hello' |> trim |> capitalize |> escape;

                  И даже если догадается, по каким кейвордам гуглить, пойдет гуглить, а не искать реиздание за этот год. То что мажорного релиза давно не было, не значит, что это не сломает зависимости с другими актуальными пакетами, то есть человеку так или иначе придется сесть на старый стек, просто потому что он купил такую книгу. Учитывая количество зависимостей в типовом JS проекте, мелкие изменения могут накопиться в немалую кучу. Такой вид информации это просто не книжный формат, вот что я пытаюсь донести.
                  Если на хабре выпустят статью по технологии 2хлетней давности, все закидают автора камнями, «як же так?!». Но почему то с книгой, за которую человек отдает деньги этот трюк не работает. Однако двойственность стандартов, не?

              Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

              Самое читаемое