ORM в Node.js: Sequelize, Prisma и когда ORM не нужен
Содержание
О проверке кода. Примеры не запускались на живой базе — в отличие от MongoDB и Redis, где был фактический вывод. Код дан по документации Sequelize и Prisma. Говорим прямо, а не ставим бейдж «проверено».
ORM обещает избавить от SQL. Разберём, что это на самом деле даёт, где ломается и почему выбор инструмента за девять лет сменился.
Что такое ORM
ORM (Object-Relational Mapping) — слой между вашими объектами и таблицами базы. Строка таблицы становится объектом, методы объекта превращаются в SQL.
// без ORM — сами пишем SQL
const [rows] = await pool.query('SELECT * FROM users WHERE id = ?', [42]);
// с ORM — работаем с объектами
const user = await User.findByPk(42);
user.name = 'Борис';
await user.save(); // ORM сгенерирует UPDATE
Идея привлекательная: думаешь объектами, а не таблицами. Проблема в том, что объекты и таблицы устроены по-разному, и абстракция протекает ровно там, где это неудобнее всего.
Sequelize: как это выглядит
import { Sequelize, DataTypes } from 'sequelize';
const sequelize = new Sequelize('sqlite::memory:');
const User = sequelize.define('User', {
name: DataTypes.STRING,
age: DataTypes.INTEGER,
});
await sequelize.sync();
await User.create({ name: 'Аня', age: 30 });
const adults = await User.findAll({ where: { age: { [Op.gte]: 18 } } });
Схема описывается моделью, запросы — методами. Пока задачи простые — CRUD, фильтры, — это читается лучше SQL.
Проблема N+1: главная ловушка ORM
Самая частая причина, по которой ORM-приложение внезапно тормозит. Выглядит безобидно:
// ❌ N+1: один запрос на список + по одному на каждого
const users = await User.findAll(); // 1 запрос
for (const user of users) {
const posts = await user.getPosts(); // +1 запрос НА КАЖДОГО
}
// 100 пользователей = 101 запрос к базе
Код чистый, логика очевидная — а база получает сто один запрос вместо одного. На разработке с десятью записями незаметно; в проде на тысячах — стена.
// ✅ жадная загрузка: один запрос с JOIN
const users = await User.findAll({ include: Post });
N+1 — это оборотная сторона удобства ORM. Он так хорошо прячет SQL, что вы не видите, сколько запросов на самом деле уходит. Лечится жадной загрузкой (include), но сначала проблему надо заметить — а для этого нужно смотреть на реальные запросы, как explain в MongoDB показывает COLLSCAN.
Дырявая абстракция
Здесь работает тот же урок, что и с Waterline в Sails: абстракция над базой протекает на сложных запросах.
ORM на своём месте
Простые операции
- CRUD: создать, найти по id, обновить
- Фильтры по полям
- Связи один-ко-многим с
include - Миграции схемы
- Защита от инъекций из коробки
Честнее сырой SQL
Сложные запросы
- Аналитика, агрегации, GROUP BY
- Оконные функции, CTE
- Хитрые многотабличные JOIN
- Тонкая оптимизация под план запроса
- ORM тут либо тормозит, либо не выражает
Практический вывод: как только запрос перестаёт выражаться методами ORM, вы пишете сырой SQL — но уже через прослойку, которая ничего не даёт, только мешает отлаживать. В этот момент ORM из помощника превращается в лишний слой.
Безопасность: работает, пока не обходите
ORM экранирует параметры автоматически — в этом его плюс:
// ✅ безопасно: ORM подставит значение как параметр
User.findAll({ where: { email: userInput } });
Но защита держится ровно до сырого SQL со строковой подстановкой:
// ❌ дыра, даже внутри ORM
sequelize.query(`SELECT * FROM users WHERE email = '${userInput}'`);
// ✅ и здесь параметры
sequelize.query('SELECT * FROM users WHERE email = ?', { replacements: [userInput] });
Принцип тот же, что везде на этом сайте — execFile, параметризованный SQL, textContent: безопасность даёт не фильтрация, а то, что данные не попадают в текст запроса. ORM это делает за вас, пока вы не обходите его руками.
Что выбрать в 2026
Инструментов стало больше, и Sequelize — уже не единственный ответ:
| Инструмент | Чем берёт |
|---|---|
| Prisma | типобезопасность, генерируемый клиент, современный API |
| Drizzle | лёгкий, ближе к SQL, отличная типизация |
| Sequelize | зрелый, много кода в проде, но воспринимается как наследие |
| Kysely | не ORM, а типобезопасный конструктор запросов |
| чистый драйвер | mysql2, полный контроль |
Ключевой сдвиг с 2017 года — типобезопасность. Prisma и Drizzle не просто генерируют SQL, а дают типы: опечатка в имени поля становится ошибкой на этапе сборки, а не багом в проде. Это и есть главная причина, по которой новые проекты уходят от Sequelize.
И честная альтернатива всему: для простого приложения драйвера mysql2 достаточно. SQL вы пишете сами — но видите каждый запрос и не ловите N+1 внезапно.
Что было на этой странице в 2017 году
Первая версия вышла 29 июля 2017 года и учила Sequelize как основной способ работать с реляционной базой из Node. Для 2017 года — верно: альтернатив зрелого уровня почти не было, а писать SQL руками считалось прошлым веком.
Что изменилось:
- Sequelize из стандарта стал одним из вариантов — и не первым. Появились Prisma и Drizzle с типобезопасностью, которой в 2017-м просто не существовало.
- Отношение к сырому SQL изменилось. Тогда его избегали; сегодня понятно, что на сложных запросах он честнее ORM, а не «примитивнее».
- N+1 и дырявость абстракции в статье не упоминались вовсе — а это ровно то, обо что спотыкаются в реальных проектах.
Мы переписали урок не как «руководство по Sequelize», а как разговор о том, что такое ORM, где он помогает и где мешает — потому что сегодня главный вопрос не «как настроить Sequelize», а «нужен ли ORM вообще для этой задачи».
Смежные темы: MySQL напрямую — жизнь без ORM; что такое MongoDB — когда база вообще не реляционная; Sails.js — про дырявость абстракции. Полный список — в справочнике по Node.js.
Частые вопросы
Что такое ORM простыми словами?
user.save() вместо INSERT INTO users, а ORM генерирует SQL. Удобно, пока запросы простые.