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

База данных делает данные постоянными: после перезапуска сервера они не исчезают.
«Массив в памяти живёт до перезапуска. База — это память, которая переживёт падения и рестарты.»

До сих пор мы хранили данные в массиве — они пропадали при каждом перезапуске сервера. Реальные приложения используют базу данных. В этом уроке разберём, какие базы бывают, как Express с ними общается через драйверы и ORM, и почему запросы к базе обязательно асинхронны.

SQL или NoSQL

Два больших мира хранения данных:

ТипПримерыМодель данных
SQL (реляционные)PostgreSQL, MySQLтаблицы со строгой схемой
NoSQL (документные)MongoDBгибкие документы (как JSON)

Express не привязан ни к одной базе: он общается с ней через библиотеку-драйвер или ORM. Выбор зависит от задачи, но для большинства веб-приложений подходит и то, и другое.

Драйвер или ORM

Есть два способа работать с базой. Драйвер даёт сырые запросы, ORM — объектный интерфейс поверх них. Современный выбор — ORM вроде Prisma или ODM Mongoose:

// Пример с Prisma (ORM): запросы выглядят как методы
const user = await prisma.user.findUnique({ where: { id: 1 } });
const created = await prisma.user.create({ data: { email: '[email protected]' } });

// Пример с Mongoose (MongoDB): модели и схемы
const user = await User.findById('...');
const created = await User.create({ email: '[email protected]' });

Главное, что роднит все эти вызовы, — они асинхронны и возвращают промисы. Запрос к базе — это ввод-вывод, а значит, его нельзя ждать синхронно.

Все обращения к базе через await

Смоделируем асинхронный доступ к данным в браузере: "база" отвечает не сразу, и код ждёт через async/await — ровно как с настоящим хранилищем:

// имитация базы: данные приходят с задержкой
function fakeDb(id) {
  return new Promise(resolve => {
    setTimeout(() => resolve({ id, name: 'Аня' }), 100);
  });
}

async function getUser(id) {
  console.log('Запрос отправлен...');
  const user = await fakeDb(id);   // ждём ответ базы
  console.log('Получено:', user.name);
  return user;
}

getUser(1);
console.log('Этот лог раньше: код не блокируется');

Видишь порядок? Сервер не ждёт базу впустую — пока идёт запрос, event loop свободен для других клиентов. Это прямое следствие того, что мы изучали про event loop.

Как работает под капотом

Драйвер базы открывает сетевое соединение (или пул соединений) и шлёт запросы по нему. Поскольку это сеть — операция асинхронная: драйвер возвращает промис, который разрешится, когда придёт ответ. ORM добавляет сверху удобный слой, превращая твои вызовы методов в SQL или запросы к Mongo и обратно — результат в объекты. Пул соединений переиспользует подключения, чтобы не открывать новое на каждый запрос.

Частые ошибки

  • Забыть await. Без него ты получишь промис вместо данных, и дальнейший код сломается.
  • Открывать соединение на каждый запрос. Используй пул, который ORM настраивает за тебя.
  • Хранить строку подключения в коде. Это секрет — ему место в переменных окружения.

Best practices

  • Работай с базой только асинхронно, через async/await.
  • Выбирай ORM (Prisma) или ODM (Mongoose) для удобства и безопасности от инъекций.
  • Держи строку подключения в переменной окружения, а доступ к данным — в слое сервисов.

Итоги

База делает данные постоянными, а Express общается с ней через драйвер или ORM — всегда асинхронно. Это естественно ложится на модель event loop. Дальше построим первую защищённую часть приложения: регистрацию пользователя с безопасным хранением пароля.

Миграции и схема данных

Структура базы со временем меняется: добавляются поля, таблицы, индексы. Менять её вручную на работающем сервере опасно и невоспроизводимо, поэтому изменения схемы оформляют миграциями — версионированными скриптами, которые накатывают и при необходимости откатывают по шагам. ORM вроде Prisma генерирует миграции из описания моделей и хранит историю, так что у всех окружений — от ноутбука разработчика до прода — гарантированно одинаковая схема. Воспринимай миграции как git для структуры базы: они дают тот же контроль версий, аудит изменений и возможность безопасно работать командой над одной схемой.

Проверьте себя
1. Почему все запросы к базе данных асинхронны?
AТак требует синтаксис JavaScript
BЭто операция ввода-вывода, и блокировать event loop нельзя
CORM работают только асинхронно из-за лицензии
DБазы данных всегда медленные
2. Что даёт ORM вроде Prisma поверх сырого драйвера?
AУдваивает скорость сети
BОбъектный интерфейс к данным и защиту от части инъекций
CЗаменяет саму базу данных
DДелает запросы синхронными