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

Где по-настоящему живут данные приложения и как 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Так быстрее печатать код
BЭто сетевой ввод-вывод, и Node не блокирует поток на ожидании
CБД этого требует синтаксически
DЧтобы экономить память
2. Чем ORM отличается от драйвера?
AORM работает быстрее всегда
BORM отображает данные на объекты и генерирует запросы, драйвер требует писать запросы вручную
CДрайвер не нужен при ORM никогда
DРазницы нет
3. Как безопасно подставить пользовательский ввод в SQL-запрос?
AСклеить строку через +
BИспользовать параметры-плейсхолдеры ($1, ?)
CЗашифровать запрос
DПеревести в верхний регистр
Поддержать проект