Подключение к базе данных: обзор

Где по-настоящему живут данные приложения и как Node с ними общается.

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

Почему массива недостаточно

В прошлых уроках задачи лежали в массиве в памяти. Это годится для учёбы, но в продакшене данные исчезнут при перезапуске и не разделятся между серверами. База данных решает обе проблемы: данные сохраняются на диск и доступны всем.

Два больших семейства баз

SQL (реляционные)NoSQL
PostgreSQL, MySQL, SQLiteMongoDB, Redis
таблицы, строки, связидокументы, ключ-значение
строгая схемагибкая схема
язык SQLAPI/запросы драйвера

Драйвер или ORM

Из Node к базе обращаются двумя способами:

  • Драйвер — низкоуровневый пакет, вы пишете запросы сами (например, pg для PostgreSQL, mongodb для Mongo).
  • ORM/ODM — библиотека, которая отображает строки/документы на объекты JavaScript и сама генерирует запросы (Prisma, Sequelize, Mongoose).

Так выглядит запрос через драйвер pg (код использует npm-пакет, поэтому не запускается в браузере):

const { Pool } = require("pg");
const pool = new Pool({ connectionString: process.env.DATABASE_URL });

async function getTasks() {
  const result = await pool.query("SELECT * FROM tasks");
  return result.rows;
}

Обратите внимание: pool.query возвращает промис — обращение к БД всегда асинхронно (это сетевой ввод-вывод, как раз то, для чего Node и неблокирующий).

Пул соединений

Открывать новое соединение с БД на каждый запрос дорого. Поэтому используют пул — заранее открытый набор соединений, которые переиспользуются. Pool в примере выше как раз про это.

Защита от SQL-инъекций

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

// ОПАСНО — не делайте так:
// pool.query("SELECT * FROM users WHERE id = " + req.params.id)

// БЕЗОПАСНО — параметр через плейсхолдер:
pool.query("SELECT * FROM users WHERE id = $1", [req.params.id]);

Результат запроса — обычные объекты

Что бы ни вернула БД, в Node это станет обычными JS-объектами, с которыми работаешь как всегда:

// имитируем result.rows из БД
const rows = [
  { id: 1, title: "Купить хлеб", done: false },
  { id: 2, title: "Позвонить врачу", done: true }
];

const pending = rows.filter(r => !r.done);
console.log("Невыполненных задач:", pending.length);
console.log("Заголовки:", rows.map(r => r.title));

Вывод:

Невыполненных задач: 1
Заголовки: [ 'Купить хлеб', 'Позвонить врачу' ]

Итог

  • Реальные данные хранят в БД: они переживают перезапуск и общие для процессов.
  • Базы делятся на SQL (таблицы) и NoSQL (документы/ключ-значение).
  • Из Node работают через драйвер (pg, mongodb) или ORM (Prisma, Sequelize).
  • Запросы к БД асинхронны; используйте пул соединений и параметры от инъекций.
Проверьте себя
1. Почему запросы к базе данных в Node асинхронны?
AЭто сетевой ввод-вывод, и Node не блокирует поток на ожидании
BТак быстрее печатать код
CБД этого требует синтаксически
DЧтобы экономить память
2. Чем ORM отличается от драйвера?
AORM работает быстрее всегда
BORM отображает данные на объекты и генерирует запросы, драйвер требует писать запросы вручную
CДрайвер не нужен при ORM никогда
DРазницы нет
3. Как безопасно подставить пользовательский ввод в SQL-запрос?
AСклеить строку через +
BЗашифровать запрос
CИспользовать параметры-плейсхолдеры ($1, ?)
DПеревести в верхний регистр