[Перевод] Руководство по Node.js, часть 10: стандартные модули, потоки, базы данных, NODE_ENV

Этот материал завершает серию переводов руководства по Node.js. Сегодня мы поговорим о модулях os, events и http, обсудим работу с потоками и базами данных, затронем вопрос использования Node.js при разработке приложений и в продакшне.

hedi2j_qyfbnwfo_jqnnqd3ub6o.png

[Советуем почитать] Другие части цикла
Часть 1: Общие сведения и начало работы
Часть 2: JavaScript, V8, некоторые приёмы разработки
Часть 3: Хостинг, REPL, работа с консолью, модули
Часть 4: npm, файлы package.json и package-lock.json
Часть 5: npm и npx
Часть 6: цикл событий, стек вызовов, таймеры
Часть 7: асинхронное программирование
Часть 8: Руководство по Node.js, часть 8: протоколы HTTP и WebSocket
Часть 9: Руководство по Node.js, часть 9: работа с файловой системой
Часть 10: Руководство по Node.js, часть 10: стандартные модули, потоки, базы данных, NODE_ENV


Модуль Node.js os


Модуль os даёт доступ ко многим функциям, которые можно использовать для получения информации об операционной системе и об аппаратном обеспечении компьютера, на котором работает Node.js. Это стандартный модуль, устанавливать его не надо, для работы с ним из кода его достаточно подключить:

const os = require('os')


Здесь имеются несколько полезных свойств, которые, в частности, могут пригодиться при работе с файлами.

Так, свойство os.EOL позволяет узнать используемый в системе разделитель строк (признак конца строки). В Linux и macOS это \n, в Windows — \r\n.

Надо отметить, что упоминая тут «Linux и macOS», мы говорим о POSIX-совместимых платформах. Ради краткости изложения менее популярные платформы мы тут не упоминаем.

Свойство os.constants.signals даёт сведения о константах, используемых для обработки сигналов процессов наподобие SIGHUP, SIGKILL, и так далее. Здесь можно найти подробности о них.

Свойство os.constants.errno содержит константы, используемые для сообщений об ошибках — наподобие EADDRINUSE, EOVERFLOW.

Теперь рассмотрим основные методы модуля os.

▍os.arch ()


Этот метод возвращает строку, идентифицирующую архитектуру системы, например — arm, x64, arm64.

▍os.cpus ()


Возвращает информацию о процессорах, доступных в системе. Например, эти сведения могут выглядеть так:

[ { model: 'Intel(R) Core(TM)2 Duo CPU     P8600  @ 2.40GHz',
    speed: 2400,
    times:
     { user: 281685380,
       nice: 0,
       sys: 187986530,
       idle: 685833750,
       irq: 0 } },
  { model: 'Intel(R) Core(TM)2 Duo CPU     P8600  @ 2.40GHz',
    speed: 2400,
    times:
     { user: 282348700,
       nice: 0,
       sys: 161800480,
       idle: 703509470,
       irq: 0 } } ]


▍os.endianness ()


Возвращает BE или LE в зависимости от того, какой порядок байтов (Big Engian или Little Endian) был использован для компиляции бинарного файла Node.js.

▍os.freemem ()


Возвращает количество свободной системной памяти в байтах.

▍os.homedir ()


Возвращает путь к домашней директории текущего пользователя. Например — '/Users/flavio'.

▍os.hostname ()


Возвращает имя хоста.

▍os.loadavg ()


Возвращает, в виде массива, данные о средних значениях нагрузки, вычисленные операционной системой. Эта информация имеет смысл только в Linux и macOS. Выглядеть она может так:

[ 3.68798828125, 4.00244140625, 11.1181640625 ]


▍os.networkInterfaces ()


Возвращает сведения о сетевых интерфейсах, доступных в системе. Например:

{ lo0:
   [ { address: '127.0.0.1',
       netmask: '255.0.0.0',
       family: 'IPv4',
       mac: 'fe:82:00:00:00:00',
       internal: true },
     { address: '::1',
       netmask: 'ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff',
       family: 'IPv6',
       mac: 'fe:82:00:00:00:00',
       scopeid: 0,
       internal: true },
     { address: 'fe80::1',
       netmask: 'ffff:ffff:ffff:ffff::',
       family: 'IPv6',
       mac: 'fe:82:00:00:00:00',
       scopeid: 1,
       internal: true } ],
  en1:
   [ { address: 'fe82::9b:8282:d7e6:496e',
       netmask: 'ffff:ffff:ffff:ffff::',
       family: 'IPv6',
       mac: '06:00:00:02:0e:00',
       scopeid: 5,
       internal: false },
     { address: '192.168.1.38',
       netmask: '255.255.255.0',
       family: 'IPv4',
       mac: '06:00:00:02:0e:00',
       internal: false } ],
  utun0:
   [ { address: 'fe80::2513:72bc:f405:61d0',
       netmask: 'ffff:ffff:ffff:ffff::',
       family: 'IPv6',
       mac: 'fe:80:00:20:00:00',
       scopeid: 8,
       internal: false } ] }


▍os.platform ()


Возвращает сведения о платформе, для которой был скомпилирован Node.js. Вот некоторые из возможных возвращаемых значений:

  • darwin
  • freebsd
  • linux
  • openbsd
  • win32


▍os.release ()


Возвращает строку, идентифицирующую номер релиза операционной системы.

▍os.tmpdir ()


Возвращает путь к заданной в системе директории для хранения временных файлов.

▍os.totalmem ()


Возвращает общее количество системной памяти в байтах.

▍os.type ()


Возвращает сведения, позволяющие идентифицировать операционную систему. Например:

  • Linux — Linux.
  • Darwin — macOS.
  • Windows_NT — Windows.


▍os.uptime ()


Возвращает время работы системы в секундах с последней перезагрузки.

Модуль Node.js events


Модуль events предоставляет нам класс EventEmitter, который предназначен для работы с событиями на платформе Node.js. Мы уже немного говорили об этом модуле в седьмой части этой серии материалов. Вот документация к нему. Здесь рассмотрим API этого модуля. Напомним, что для использования его в коде нужно, как это обычно бывает со стандартными модулями, его подключить. После этого надо создать новый объект EventEmitter. Выглядит это так:

const EventEmitter = require('events')
const door = new EventEmitter()


Объект класса EventEmitter пользуется стандартными механизмами, в частности — следующими событиями:

  • newListener — это событие вызывается при добавлении обработчика событий.
  • removeListener — вызывается при удалении обработчика.


Рассмотрим наиболее полезные методы объектов класса EventEmitter (подобный объект в названиях методов обозначен как emitter).

▍emitter.addListener ()


Псевдоним для метода emitter.on().

▍emitter.emit ()


Генерирует событие. Синхронно вызывает все обработчики события в том порядке, в котором они были зарегистрированы.

▍emitter.eventNames ()


Возвращает массив, который содержит зарегистрированные события.

▍emitter.getMaxListeners ()


Возвращает максимальное число обработчиков, которые можно добавить к объекту класса EventEmitter. По умолчанию это 10. При необходимости этот параметр можно увеличить или уменьшить с использованием метода setMaxListeners().

▍emitter.listenerCount ()


Возвращает количество обработчиков события, имя которого передаётся данному методу в качестве параметра:

door.listenerCount('open')


▍emitter.listeners ()


Возвращает массив обработчиков события для соответствующего события, имя которого передано этому методу:

door.listeners('open')


▍emitter.off ()


Псевдоним для метода emitter.removeListener(), появившийся в Node 10.

▍emitter.on ()


Регистрируеn коллбэк, который вызывается при генерировании события. Вот как им пользоваться:

door.on('open', () => {
  console.log('Door was opened')
})


▍emitter.once ()


Регистрирует коллбэк, который вызывается только один раз — при первом возникновении события, для обработки которого зарегистрирован этот коллбэк. Например:

const EventEmitter = require('events')
const ee = new EventEmitter()
ee.once('my-event', () => {
  //вызвать этот коллбэк один раз при первом возникновении события
})


▍emitter.prependListener ()


При регистрации обработчика с использованием методов on() или addListener() этот обработчик добавляется в конец очереди обработчиков и вызывается для обработки соответствующего события последним. При использовании метода prependListener() обработчик добавляется в начало очереди, что приводит к тому, что он будет вызываться для обработки события первым.

▍emitter.prependOnceListener ()


Этот метод похож на предыдущий. А именно, когда обработчик, предназначенный для однократного вызова, регистрируется с помощью метода once(), он оказывается последним в очереди обработчиков и последним вызывается. Метод prependOnceListener() позволяет добавить такой обработчик в начало очереди.

▍emitter.removeAllListeners ()


Данный метод удаляет все обработчики для заданного события, зарегистрированные в соответствующем объекте. Пользуются им так:

door.removeAllListeners('open')


▍emitter.removeListener ()


Удаляет заданный обработчик, который нужно передать данному методу. Для того чтобы сохранить обработчик для последующего удаления соответствующий коллбэк можно назначить переменной. Выглядит это так:

const doSomething = () => {}
door.on('open', doSomething)
door.removeListener('open', doSomething)


▍emitter.setMaxListeners ()


Этот метод позволяет задать максимальное количество обработчиков, которые можно добавить к отдельному событию в экземпляре класса EventEmitter. По умолчанию, как уже было сказано, можно добавить до 10 обработчиков для конкретного события. Это значение можно изменить. Пользуются данным методом так:

door.setMaxListeners(50)


Модуль Node.js http


В восьмой части этой серии материалов мы уже говорили о стандартном модуле Node.js http. Он даёт в распоряжение разработчика механизмы, предназначенные для создания HTTP-серверов. Он является основным модулем, применяемым для решения задач обмена данными по сети в Node.js. Подключить его в коде можно так:

const http = require('http')


В его состав входят свойства, методы и классы. Поговорим о них.

▍Свойства


http.METHODS


В этом свойстве перечисляются все поддерживаемые методы HTTP:

> require('http').METHODS
[ 'ACL',
  'BIND',
  'CHECKOUT',
  'CONNECT',
  'COPY',
  'DELETE',
  'GET',
  'HEAD',
  'LINK',
  'LOCK',
  'M-SEARCH',
  'MERGE',
  'MKACTIVITY',
  'MKCALENDAR',
  'MKCOL',
  'MOVE',
  'NOTIFY',
  'OPTIONS',
  'PATCH',
  'POST',
  'PROPFIND',
  'PROPPATCH',
  'PURGE',
  'PUT',
  'REBIND',
  'REPORT',
  'SEARCH',
  'SUBSCRIBE',
  'TRACE',
  'UNBIND',
  'UNLINK',
  'UNLOCK',
  'UNSUBSCRIBE' ]


http.STATUS_CODES


Здесь содержатся коды состояния HTTP и их описания:

> require('http').STATUS_CODES
{ '100': 'Continue',
  '101': 'Switching Protocols',
  '102': 'Processing',
  '200': 'OK',
  '201': 'Created',
  '202': 'Accepted',
  '203': 'Non-Authoritative Information',
  '204': 'No Content',
  '205': 'Reset Content',
  '206': 'Partial Content',
  '207': 'Multi-Status',
  '208': 'Already Reported',
  '226': 'IM Used',
  '300': 'Multiple Choices',
  '301': 'Moved Permanently',
  '302': 'Found',
  '303': 'See Other',
  '304': 'Not Modified',
  '305': 'Use Proxy',
  '307': 'Temporary Redirect',
  '308': 'Permanent Redirect',
  '400': 'Bad Request',
  '401': 'Unauthorized',
  '402': 'Payment Required',
  '403': 'Forbidden',
  '404': 'Not Found',
  '405': 'Method Not Allowed',
  '406': 'Not Acceptable',
  '407': 'Proxy Authentication Required',
  '408': 'Request Timeout',
  '409': 'Conflict',
  '410': 'Gone',
  '411': 'Length Required',
  '412': 'Precondition Failed',
  '413': 'Payload Too Large',
  '414': 'URI Too Long',
  '415': 'Unsupported Media Type',
  '416': 'Range Not Satisfiable',
  '417': 'Expectation Failed',
  '418': 'I\'m a teapot',
  '421': 'Misdirected Request',
  '422': 'Unprocessable Entity',
  '423': 'Locked',
  '424': 'Failed Dependency',
  '425': 'Unordered Collection',
  '426': 'Upgrade Required',
  '428': 'Precondition Required',
  '429': 'Too Many Requests',
  '431': 'Request Header Fields Too Large',
  '451': 'Unavailable For Legal Reasons',
  '500': 'Internal Server Error',
  '501': 'Not Implemented',
  '502': 'Bad Gateway',
  '503': 'Service Unavailable',
  '504': 'Gateway Timeout',
  '505': 'HTTP Version Not Supported',
  '506': 'Variant Also Negotiates',
  '507': 'Insufficient Storage',
  '508': 'Loop Detected',
  '509': 'Bandwidth Limit Exceeded',
  '510': 'Not Extended',
  '511': 'Network Authentication Required' }


http.globalAgent


Данное свойство указывает на глобальный экземпляр класса http.Agent. Он используется для управления соединениями. Его можно считать ключевым компонентом HTTP-подсистемы Node.js. Подробнее о классе http.Agent мы поговорим ниже.

▍Методы


http.createServer ()


Возвращает новый экземпляр класса http.Server. Вот как пользоваться этим методом для создания HTTP-сервера:

const server = http.createServer((req, res) => {
  //в этом коллбэке будут обрабатываться запросы
})


http.request ()


Позволяет выполнить HTTP-запрос к серверу, создавая экземпляр класса http.ClientRequest.

http.get ()


Этот метод похож на http.request(), но он автоматически устанавливает метод HTTP в значение GET и автоматически же вызывает команду вида req.end().

▍Классы


Модуль HTTP предоставляет 5 классов — Agent, ClientRequest, Server, ServerResponse и IncomingMessage. Рассмотрим их.

http.Agent


Глобальный экземпляр класса http.Agent, создаваемый Node.js, используется для управления соединениями. Он применяется в качестве значения по умолчанию всеми HTTP-запросами и обеспечивает постановку запросов в очередь и повторное использование сокетов. Кроме того, он поддерживает пул сокетов, что позволяет обеспечить высокую производительность сетевой подсистемы Node.js. При необходимости можно создать собственный объект http.Agent.

http.ClientRequest


Объект класса http.ClientRequest, представляющий собой выполняющийся запрос, создаётся при вызове методов http.request() или http.get(). При получении ответа на запрос вызывается событие response, в котором передаётся ответ — экземпляр http.IncomingMessage. Данные, полученные после выполнения запроса, можно обработать двумя способами:

  • Можно вызвать метод response.read().
  • В обработчике события response можно настроить прослушиватель для события data, что позволяет работать с потоковыми данными.


http.Server


Экземпляры этого класса используются для создания серверов с применением команды http.createServer(). После того, как у нас имеется объект сервера, мы можем воспользоваться его методами:

  • Метод listen() используется для запуска сервера и организации ожидания и обработки входящих запросов.
  • Метод close() останавливает сервер.


http.ServerResponse


Этот объект создаётся классом http.Server и передаётся в качестве второго параметра событию request при его возникновении. Обычно подобным объектам в коде назначают имя res:

const server = http.createServer((req, res) => {
  //res - это объект http.ServerResponse
})


В таких обработчиках, после того, как ответ сервера будет готов к отправке клиенту, вызывают метод end(), завершающий формирование ответа. Этот метод необходимо вызывать после завершения формирования каждого ответа.

Вот методы, которые используются для работы с HTTP-заголовками:

  • getHeaderNames() — возвращает список имён установленных заголовков.
  • getHeaders() — возвращает копию установленных HTTP-заголовков.
  • setHeader('headername', value) — устанавливает значение для заданного заголовка.
  • getHeader('headername') — возвращает установленный заголовок.
  • removeHeader('headername') — удаляет установленный заголовок.
  • hasHeader('headername') — возвращает true если в ответе уже есть заголовок, имя которого передано этому методу.
  • headersSent() — возвращает true если заголовки уже отправлены клиенту.


После обработки заголовков их можно отправить клиенту, вызвав метод response.writeHead(), который, в качестве первого параметра, принимает код состояния. В качестве второго и третьего параметров ему можно передать сообщение, соответствующее коду состояния, и заголовки.

Для отправки данных клиенту в теле ответа используют метод write(). Он отправляет буферизованные данные в поток HTTP-ответа.

Если до этого заголовки ещё не были установлены командой response.writeHead(), сначала будут отправлены заголовки с кодом состояния и сообщением, которые заданы в запросе. Задавать их значения можно, устанавливая значения для свойств statusCode и statusMessage:

response.statusCode = 500
response.statusMessage = 'Internal Server Error'


http.IncomingMessage


Объект класса http.IncomingMessage создаётся в ходе работы следующих механизмов:

  • http.Server — при обработке события request.
  • http.ClientRequest — при обработке события response.


Его можно использовать для работы с данными ответа. А именно:

  • Для того чтобы узнать код состояния ответа и соответствующее сообщение используются свойства statusCode и statusMessage.
  • Заголовки ответа можно посмотреть, обратившись к свойству headers или rawHearders (для получения списка необработанных заголовков).
  • Метод запроса можно узнать, воспользовавшись свойством method.
  • Узнать используемую версию HTTP можно с помощью свойства httpVersion.
  • Для получения URL предназначено свойство url.
  • Свойство socket позволяет получить объект net.Socket, связанный с соединением.


Данные ответа представлены в виде потока так как объект http.IncomingMessage реализует интерфейс Readable Stream.

Работа с потоками в Node.js


Потоки — это одна из фундаментальных концепций, используемых в Node.js-приложениях. Потоки — это инструменты, которые позволяют выполнять чтение и запись файлов, организовывать сетевое взаимодействие систем, и, в целом — эффективно реализовывать операции обмена данными.

Концепция потоков не уникальна для Node.js. Они появились в ОС семейства Unix десятки лет назад. В частности, программы могут взаимодействовать друг с другом, передавая потоки данных с использованием конвейеров (с применением символа конвейера — |).

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

Благодаря использованию механизма потоков файлы можно считывать и обрабатывать по частям, что избавляет от необходимости хранить в памяти большие объёмы данных.

Модуль Node.js stream представляет собой основу, на которой построены все API, поддерживающие работу с потоками.

▍О сильных сторонах использования потоков


Потоки, в сравнении с другими способами обработки данных, отличаются следующими преимуществами:

  • Эффективное использование памяти. Работа с потоком не предполагает хранения в памяти больших объёмов данных, загружаемых туда заранее, до того, как появится возможность их обработать.
  • Экономия времени. Данные, получаемые из потока, можно начать обрабатывать гораздо быстрее, чем в случае, когда для того, чтобы приступить к их обработке, приходится ждать их полной загрузки.


▍Пример работы с потоками


Традиционный пример работы с потоками демонстрирует чтение файла с диска.

Сначала рассмотрим код, в котором потоки не используются. Стандартный модуль Node.js fs позволяет прочитать файл, после чего его можно передать по протоколу HTTP в ответ на запрос, полученный HTTP-сервером:

const http = require('http')
const fs = require('fs')
const server = http.createServer(function (req, res) {
  fs.readFile(__dirname + '/data.txt', (err, data) => {
    res.end(data)
  })
})
server.listen(3000)


Метод readFile(), использованный здесь, позволяет прочесть файл целиком. Когда чтение будет завершено, он вызывает соответствующий коллбэк.

Метод res.end(data), вызываемый в коллбэке, отправляет содержимое файла клиенту.

Если размер файла велик, то эта операция займёт немало времени. Вот тот же пример переписанный с использованием потоков:

const http = require('http')
const fs = require('fs')
const server = http.createServer((req, res) => {
  const stream = fs.createReadStream(__dirname + '/data.txt')
  stream.pipe(res)
})
server.listen(3000)


Вместо того, чтобы ждать того момента, когда файл будет полностью прочитан, мы начинаем передавать его данные клиенту сразу после того, как первая порция этих данных будет готова к отправке.

▍Метод pipe ()


В предыдущем примере мы использовали конструкцию вида stream.pipe(res), в которой вызывается метод файлового потока pipe(). Этот метод берёт данные из их источника и отправляет их в место назначения.

Его вызывают для потока, представляющего собой источник данных. В данном случае это — файловый поток, который отправляют в HTTP-ответ.

Возвращаемым значением метода pipe() является целевой поток. Это очень удобно, так как позволяет объединять в цепочки несколько вызовов метода pipe():

src.pipe(dest1).pipe(dest2)


Это равносильно такой конструкции:

src.pipe(dest1)
dest1.pipe(dest2)


▍API Node.js, в которых используются потоки


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

  • process.stdin — возвращает поток, подключённый к stdin.
  • process.stdout — возвращает поток, подключённый к stdout.
  • process.stderr — возвращает поток, подключённый к stderr.
  • fs.createReadStream() — создаёт читаемый поток для работы с файлом.
  • fs.createWriteStream()— создаёт записываемый поток для работы с файлом.
  • net.connect() — инициирует соединение, основанное на потоке.
  • http.request() — возвращает экземпляр класса http.ClientRequest, предоставляющий доступ к записываемому потоку.
  • zlib.createGzip() — сжимает данные с использованием алгоритма gzip и отправляет их в поток.
  • zlib.createGunzip() — выполняет декомпрессию gzip-потока.
  • zlib.createDeflate() — сжимает данные с использованием алгоритма deflate и отправляет их в поток.
  • zlib.createInflate() — выполняет декомпрессию deflate-потока.


▍Разные типы потоков


Существует четыре типа потоков:

  • Поток для чтения (Readable) — это поток, из которого можно читать данные. Записывать данные в такой поток нельзя. Когда в такой поток поступают данные, они буферизуются до того момента пока потребитель данных не приступит к их чтению.
  • Поток для записи (Writable) — это поток, в который можно отправлять данные. Читать из него данные нельзя.
  • Дуплексный поток (Duplex) — в такой поток можно и отправлять данные и читать их из него. По существу это — комбинация потока для чтения и потока для записи.
  • Трансформирующий поток (Transform) — такие потоки похожи на дуплексные потоки, разница заключается в том, что то, что поступает на вход этих потоков, преобразует то, что из них можно прочитать.


▍Создание потока для чтения


Поток для чтения можно создать и инициализировать, воспользовавшись возможностями модуля stream:

const Stream = require('stream')
const readableStream = new Stream.Readable()


Теперь в поток можно поместить данные, которые позже сможет прочесть потребитель этих данных:

readableStream.push('hi!')
readableStream.push('ho!')


▍Создание потока для записи


Для того чтобы создать записываемый поток нужно расширить базовый объект Writable и реализовать его метод _write(). Для этого сначала создадим соответствующий поток:

const Stream = require('stream')
const writableStream = new Stream.Writable()


Затем реализуем его метод _write():

writableStream._write = (chunk, encoding, next) => {
    console.log(chunk.toString())
    next()
}


Теперь к такому потоку можно подключить поток, предназначенный для чтения:

process.stdin.pipe(writableStream)


▍Получение данных из потока для чтения


Для того чтобы получить данные из потока, предназначенного для чтения, воспользуемся потоком для записи:

const Stream = require('stream')
const readableStream = new Stream.Readable()
const writableStream = new Stream.Writable()
writableStream._write = (chunk, encoding, next) => {
    console.log(chunk.toString())
    next()
}
readableStream.pipe(writableStream)
readableStream.push('hi!')
readableStream.push('ho!')
readableStream.push(null)


Команда readableStream.push(null) сообщает об окончании вывода данных.

Работать с потоками для чтения можно и напрямую, обрабатывая событие readable:

readableStream.on('readable', () => {
  console.log(readableStream.read())
})


▍Отправка данных в поток для записи


Для отправки данных в поток для записи используется метод write():

writableStream.write('hey!\n')


▍Сообщение потоку для записи о том, что запись данных завершена


Для того чтобы сообщить потоку для записи о том, что запись данных в него завершена, можно воспользоваться его методом end():

writableStream.end()


Этот метод принимает несколько необязательных параметров. В частности, ему можно передать последнюю порцию данных, которые надо записать в поток.

Основы работы с MySQL в Node.js


MySQL является одной из самых популярных СУБД в мире. В экосистеме Node.js имеется несколько пакетов, которые позволяют взаимодействовать с MySQL-базами, то есть — сохранять в них данные, получать данные из баз и выполнять другие операции.

Мы будем использовать пакет mysqljs/mysql. Этот проект, который существует уже очень давно, собрал более 12000 звёзд на GitHub. Для того чтобы воспроизвести следующие примеры, вам понадобится MySQL-сервер.

▍Установка пакета


Для установки этого пакета воспользуйтесь такой командой:

npm install mysql


▍Инициализация подключения к базе данных


Сначала подключим пакет в программе:

const mysql = require('mysql')


После этого создадим соединение:

const options = {
  user: 'the_mysql_user_name',
  password: 'the_mysql_user_password',
  database: 'the_mysql_database_name'
}
const connection = mysql.createConnection(options)


Теперь попытаемся подключиться к базе данных:

connection.connect(err => {
  if (err) {
    console.error('An error occurred while connecting to the DB')
    throw err
  }
}


▍Параметры соединения


В вышеприведённом примере объект options содержал три параметра соединения:

const options = {
  user: 'the_mysql_user_name',
  password: 'the_mysql_user_password',
  database: 'the_mysql_database_name'
}


На самом деле этих параметров существует гораздо больше. В том числе — следующие:

  • host — имя хоста, на котором расположен MySQL-сервер, по умолчанию — localhost.
  • port — номер порта сервера, по умолчанию — 3306.
  • socketPath — используется для указания сокета Unix вместо хоста и порта.
  • debug — позволяет работать в режиме отладки, по умолчанию эта возможность отключена.
  • trace — позволяет выводить сведения о трассировке стека при возникновении ошибок, по умолчанию эта возможность включена.
  • ssl — используется для настройки SSL-подключения к серверу.


▍Выполнение запроса SELECT


Теперь всё готово к выполнению SQL-запросов к базе данных. Для выполнения запросов используется метод соединения query, который принимает запрос и коллбэк. Если операция завершится успешно — коллбэк будет вызван с передачей ему данных, полученных из базы. В случае ошибки в коллбэк попадёт соответствующий объект ошибки. Вот как это выглядит при выполнении запроса на выборку данных:

connection.query('SELECT * FROM todos', (error, todos, fields) => {
  if (error) {
    console.error('An error occurred while executing the query')
    throw error
  }
  console.log(todos)
})


При формировании запроса можно использовать значения, которые будут автоматически встроены в строку запроса:

const id = 223
connection.query('SELECT * FROM todos WHERE id = ?', [id], (error, todos, fields) => {
  if (error) {
    console.error('An error occurred while executing the query')
    throw error
  }
  console.log(todos)
})


Для передачи в запрос нескольких значений можно, в качестве второго параметра, использовать массив:

const id = 223
const author = 'Flavio'
connection.query('SELECT * FROM todos WHERE id = ? AND author = ?', [id, author], (error, todos, fields) => {
  if (error) {
    console.error('An error occurred while executing the query')
    throw error
  }
  console.log(todos)
})


▍Выполнение запроса INSERT


Запросы INSERT используются для записи данных в базу. Например, запишем в базу данных объект:

const todo = {
  thing: 'Buy the milk'
  author: 'Flavio'
}
connection.query('INSERT INTO todos SET ?', todo, (error, results, fields) => {
  if (error) {
    console.error('An error occurred while executing the query')
    throw error
  }
})


Если у таблицы, в которую добавляются данные, есть первичный ключ со свойством auto_increment, его значение будет возвращено в виде results.insertId:

const todo = {
  thing: 'Buy the milk'
  author: 'Flavio'
}
connection.query('INSERT INTO todos SET ?', todo, (error, results, fields) => {
  if (error) {
    console.error('An error occurred while executing the query')
    throw error
  }}
  const id = results.resultId
  console.log(id)
)


▍Закрытие соединения с базой данных


После того как работа с базой данных завершена и пришло время закрыть соединение — воспользуйтесь его методом end():

connection.end()


Это приведёт к правильному завершению работы с базой данных.

О разнице между средой разработки и продакшн-средой


Создавая приложения в среде Node.js можно использовать различные конфигурации для окружения разработки и продакшн-окружения.

По умолчанию платформа Node.js работает в окружении разработки. Для того чтобы указать ей на то, что код выполняется в продакшн-среде, можно настроить переменную окружения NODE_ENV:

NODE_ENV=production


Обычно это делается в командной строке. В Linux, например, это выглядит так:

export NODE_ENV=production


Лучше, однако, поместить подобную команду в конфигурационный файл наподобие .bash_profile (при использовании Bash), так как в противном случае такие настройки не сохраняются после перезагрузки системы.

Настроить значение переменной окружения можно, воспользовавшись следующей конструкцией при запуске приложения:

NODE_ENV=production node app.js


Эта переменная окружения широко используется во внешних библиотеках для Node.js. Установка NODE_ENV в значение production обычно означает следующее:

  • До минимума сокращается логирование.
  • Используется больше уровней кэширования для оптимизации производительности.


Например, Pug — библиотека для работы с шаблонами, используемая Express, готовится к работе в режиме отладки в том случае, если переменная NODE_ENV не установлена в значение production. Представления Express, в режиме разработки, генерируются при обработке каждого запроса. В продакшн-режиме они кэшируются. Есть и множество других подобных примеров.

Express предоставляет конфигурационные хуки для каждого окружения. То, какой именно будет вызван, зависит от значения NODE_ENV:

app.configure('development', () => {
  //...
})
app.configure('production', () => {
  //...
})
app.configure('production', 'staging', () => {
  //...
})


Например, с их помощью можно использовать различные обработчики событий для разных режимов:

app.configure('development', () => {
  app.use(express.errorHandler({ dumpExceptions: true, showStack: true }));
})
app.configure('production', () => {
  app.use(express.errorHandler())
})


▍Итоги


Надеемся, освоив это руководство, вы узнали о платформе Node.js достаточно много для того, чтобы приступить к работе с ней. Полагаем, теперь вы, даже если начали читать первую статью этого цикла, совершенно не разбираясь в Node.js, сможете начать писать что-то своё, с интересном читать чужой код и с толком пользоваться документацией к Node.js.

Уважаемые читатели! Если вы прочли эту серию публикаций, не имея знаний о разработке для Node.js, просим рассказать о том, как вы оцениваете свой уровень теперь.

1ba550d25e8846ce8805de564da6aa63.png

© Habrahabr.ru