Работа с базами данных

Node Hero: Глава 5

Перевод книги Node Hero от RisingStack. Переведено с разрешения правообладателей.

Оглавление

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

Хранение данных в глобальной переменной

Раздача статических страниц пользователям (о чём вы узнали в предыдущей главе) может быть подходящим вариантом для лэндингов или для личных блогов. Однако, если вы хотите отдавать персонализированный контент, вам придётся где-то хранить данные.

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

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

const users = []
app.post('/users', function (req, res) {
// извлекаем данные пользователя из тела запроса
const user = req.body
users.push({
name: user.name,
age: user.age
})
res.send('successfully registered')
})

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

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

  • оперативная память дорогая
  • память очищается каждый раз, когда вы перезапускаете ваше приложение
  • если вы не будете чистить память, иногда вы можете получить переполнение памяти

Хранение данных в файле

Следующее решение, которое может прийти вам в голову, это хранить данные в файлах.

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

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

const fs = require('fs')
app.post('/users', function (req, res) {
const user = req.body
fs.appendToFile('users.txt', JSON.stringify({name: user.name, age: user.age }), (err) => {
res.send('successfully registered')
})
})

Таким образом, мы не потеряем пользовательские данные даже после перезагрузки сервера. Это решение также экономически выгодно, так как увеличение дискового пространства дешевле, чем покупка ОЗУ.

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

  • добавление данных работает неплохо, но подумайте об обновлении или удалении
  • если мы работаем с файлами, нет простого решения для параллельного доступа к ним (системные блокировки не позволят вам писать в один файл параллельно)
  • когда мы пытаемся масштабировать наше приложение, мы не можем разделить файлы между серверами (на самом деле можем, но это далеко за пределами уровня этого руководства)

Здесь выходят на сцену настоящие базы данных.

Возможно, вы уже слышали, что существуют два основных типа баз данных: SQL и NoSQL.

SQL

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

Сами данные хранятся в таблицах. Каждая добавленная часть будет представлена в виде строки в таблице, как в Google Sheets или Microsoft Excel.

В базе данных SQL вы можете определить схемы. Они предоставят скелет для данных, которые вы собираетесь разместить. Также, перед тем, как сохранить данные, будет необходимо задать типы различных значений. Например, вам нужно будет определить таблицу для ваших пользовательских данных и сообщить базе данных, что у неё есть имя пользователя, являющееся строкой, и возраст — целый тип.

NoSQL

С другой стороны, в последнее десятилетие стали весьма популярны NoSQL базы данных. С NoSQL вам не нужно определять схему и вы можете хранить любой произвольный JSON. Это хорошо сочетается с JavaScript, потому что мы можем легко превратить любой объект в JSON. Будьте осторожны, потому что вы никогда не можете гарантировать, что данные консистентны, и вы никогда не сможете узнать, какая структура находится в базе данных.

Node.js и MongoDB

Существует распространённое заблуждение о Node.js, которое можно услышать довольно часто:

«Node.js можно использовать только с MongoDB (самая популярная NoSQL база данных)».

По моему опыту, это не так. У большинства баз данных имеются драйверы для Node.js и библиотеки в NPM. По моему мнению, они такие же простые и лёгкие в использовании, как MongoDB.

Node.js и PostgreSQL

Для простоты мы будем использовать SQL в следующем примере. Мой выбор — PostgreSQL.

Чтобы запустить PostgreSQL, вам необходимо установить его на свой компьютер. Если вы используете Mac, вы можете использовать Homebrew для установки PostgreSQL. В противном случае, если вы работаете в Linux, вы можете установить его с помощью своего диспетчера пакетов.

Для получения дополнительной информации ознакомьтесь с этим отличным руководством по началу работы с вашей первой базой данных.

Если вы планируете использовать инструмент для просмотра базы данных, я бы рекомендовал утилиту для командной строки — psql. Она поставляется вместе с сервером PostgreSQL. Вот небольшая инструкция, которая пригодится, если вы начнёте использовать psql.

Если вам не нравится интерфейс командной строки, вы можете использовать pgAdmin, который является инструментом с открытым исходным кодом и предназначен для администрирования PostgreSQL.

Обратите внимание, что SQL — это сам по себе язык программирования. Мы не будем рассматривать все его возможности, а только наиболее простые. Если вам потребуется глубже изучить SQL, то в интернете есть много отличных онлайн-курсов, охватывающих все основы PostgreSQL.

Взаимодействие Node.js с базой данных

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

Затем мы должны создать таблицу для наших пользователей.

CREATE TABLE users(
name VARCHAR(20),
age SMALLINT
);

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

‘use strict’
const pg = require('pg')
const conString = 'postgres://username:password@ localhost/node_hero' // Убедитесь, что вы указали данные от вашей базы данных
pg.connect(conString, function (err, client, done) {
if (err) {
return console.error('error fetching client from pool', err)
}
client.query('SELECT $1::varchar AS my_first_query', ['node hero'], function (err, result) {
done()
    if (err) {
return console.error('error happened during query', err)
}
console.log(result.rows[0])
process.exit(0)
})
})

Это был простой пример — “hello world” в PostgreSQL. Обратите внимание, что первым параметром является строка, которая является нашей SQL-командой, второй параметр представляет собой массив значений, которыми мы хотели бы параметризовать наш запрос.

Большой ошибкой с точки зрения безопасности был бы ввод данных, пришедших от пользователя, в том виде, в котором они были переданы. Приведённая выше функция client.query защищает вас от SQL-инъекций, являющихся распространённым видом атаки, когда злоумышленник пытается внедрить в запрос произвольный SQL-код. Всегда учитывайте это при создании любого приложения, в котором возможен ввод данных со стороны пользователя. Чтобы узнать больше, ознакомьтесь с нашим контрольным списком безопасности Node.js-приложений.

Примечание переводчика: обычно никто не пишет SQL-запросы руками, вместо этого используют так называемые конструкторы запросов (query builder), например sequelize и knex.

Давайте продолжим наш предыдущий пример.

app.post('/users', function (req, res, next) {
const user = req.body
  pg.connect(conString, function (err, client, done) {
if (err) {
// Передача ошибки в обработчик express
return next(err)
}
client.query('INSERT INTO users (name, age) VALUES ($1, $2);', [user.name, user.age], function (err, result) {
done() // Этот коллбек сигнализирует драйверу pg, что соединение может быть закрыто или возвращено в пул соединений
if (err) {
// Передача ошибки в обработчик express
return next(err)
}
res.send(200)
})
})
})

Достижение разблокировано: пользователь сохранён в базе данных! :) Теперь давайте попробуем прочитать эти данные. Затем добавим в наше приложение новый роут для поиска пользователей.

app.get('/users', function (req, res, next {
pg.connect(conString, function (err, client, done) {
if (err) {
// Передача ошибки в обработчик express
return next(err)
}
client.query('SELECT name, age FROM users;', [], function (err, result) {
done()
if (err) {
// Передача ошибки в обработчик express
return next(err)
}
res.json(result.rows)
})
})
})

Это было не так сложно, не так ли?

Теперь вы можете запустить любой сложный SQL-запрос, который вы только сможете вообразить, в вашем Node.js-приложении.

С помощью этой техники вы можете постоянно хранить данные в своём приложении, а благодаря трудолюбивой команде разработчиков модуля node-postgres это проще простого.

Мы рассмотрели все основы, которые вы должны знать об использовании баз данных в Node.js. Теперь попробуйте создать что-то самостоятельно.


Пробуйте всё и экспериментируйте, потому что это лучший способ стать настоящим героем Node.js! Практикуйтесь и будьте готовы к следующей главе о том, как общаться со сторонними API!


Слушайте наш подкаст в iTunes и SoundCloud, читайте нас на Medium, контрибьютьте на GitHub, общайтесь в группе Telegram, следите в Twitter и канале Telegram, рекомендуйте в VK и Facebook.

Глава на GitHub