- Home »

Как использовать Sequelize с Node.js и MySQL
Если тебе надоело писать чистый SQL в Node.js приложениях, возиться с connection pooling и вручную парсить результаты запросов, то Sequelize — это твой лучший друг. Это мощная ORM (Object-Relational Mapping) библиотека, которая превращает работу с базами данных в более человеческий процесс. Вместо того чтобы писать SELECT * FROM users WHERE id = 1
, ты просто делаешь User.findByPk(1)
. Звучит заманчиво? Сейчас разберём, как поднять всю эту красоту с MySQL на сервере.
В этой статье мы пройдём весь путь от установки до продакшена: настроим Sequelize, создадим модели, научимся делать миграции и посмотрим на реальные примеры использования. Особое внимание уделим подводным камням, которые могут испортить жизнь в продакшене.
Что такое Sequelize и зачем оно нужно
Sequelize — это promise-based ORM для Node.js, которая поддерживает PostgreSQL, MySQL, MariaDB, SQLite и Microsoft SQL Server. Основная фишка в том, что она позволяет работать с базой данных через JavaScript объекты, а не сырые SQL-запросы.
Основные преимущества:
- Миграции — версионирование схемы базы данных
- Валидация данных — проверка на уровне модели
- Ассоциации — связи между таблицами через JavaScript
- Connection pooling — из коробки
- Транзакции — удобная работа с ACID
Установка и базовая настройка
Для начала нужен сервер с Node.js и MySQL. Если у тебя ещё нет VPS, можно быстро заказать здесь.
Создаём новый проект и устанавливаем зависимости:
mkdir sequelize-demo
cd sequelize-demo
npm init -y
npm install sequelize mysql2
npm install --save-dev sequelize-cli
Sequelize CLI — это отдельный пакет для работы с миграциями и моделями через командную строку. Инициализируем проект:
npx sequelize-cli init
Команда создаст следующую структуру:
config/
config.json
migrations/
models/
index.js
seeders/
Теперь настроим подключение к MySQL. Редактируем config/config.json
:
{
"development": {
"username": "your_username",
"password": "your_password",
"database": "sequelize_demo",
"host": "localhost",
"dialect": "mysql",
"logging": console.log
},
"production": {
"username": "your_username",
"password": "your_password",
"database": "sequelize_demo_prod",
"host": "localhost",
"dialect": "mysql",
"logging": false,
"pool": {
"max": 20,
"min": 0,
"acquire": 30000,
"idle": 10000
}
}
}
Создание первой модели
Создадим модель User с миграцией:
npx sequelize-cli model:generate --name User --attributes firstName:string,lastName:string,email:string,age:integer
Команда создаст файл модели в models/user.js
и миграцию в migrations/
. Посмотрим на модель:
'use strict';
const { Model } = require('sequelize');
module.exports = (sequelize, DataTypes) => {
class User extends Model {
static associate(models) {
// Здесь будут ассоциации
}
}
User.init({
firstName: {
type: DataTypes.STRING,
allowNull: false,
validate: {
len: [2, 50]
}
},
lastName: {
type: DataTypes.STRING,
allowNull: false,
validate: {
len: [2, 50]
}
},
email: {
type: DataTypes.STRING,
allowNull: false,
unique: true,
validate: {
isEmail: true
}
},
age: {
type: DataTypes.INTEGER,
validate: {
min: 18,
max: 120
}
}
}, {
sequelize,
modelName: 'User',
tableName: 'users',
timestamps: true
});
return User;
};
Применяем миграцию:
npx sequelize-cli db:migrate
Работа с данными
Создаём файл app.js
для экспериментов:
const { Sequelize } = require('sequelize');
const { User } = require('./models');
async function main() {
try {
// Создание пользователя
const user = await User.create({
firstName: 'John',
lastName: 'Doe',
email: 'john@example.com',
age: 30
});
console.log('Пользователь создан:', user.toJSON());
// Поиск пользователя
const foundUser = await User.findOne({
where: { email: 'john@example.com' }
});
console.log('Найденный пользователь:', foundUser.toJSON());
// Обновление
await User.update(
{ age: 31 },
{ where: { id: user.id } }
);
// Поиск всех пользователей
const allUsers = await User.findAll({
attributes: ['id', 'firstName', 'lastName', 'email'],
order: [['createdAt', 'DESC']],
limit: 10
});
console.log('Все пользователи:', allUsers.map(u => u.toJSON()));
} catch (error) {
console.error('Ошибка:', error);
}
}
main();
Ассоциации между моделями
Создадим модель Posts и свяжем её с User:
npx sequelize-cli model:generate --name Post --attributes title:string,content:text,userId:integer
Настроим ассоциации в моделях. В models/user.js
:
static associate(models) {
User.hasMany(models.Post, {
foreignKey: 'userId',
as: 'posts'
});
}
В models/post.js
:
static associate(models) {
Post.belongsTo(models.User, {
foreignKey: 'userId',
as: 'author'
});
}
Теперь можно делать запросы с join’ами:
// Получить пользователя с его постами
const userWithPosts = await User.findByPk(1, {
include: [{
model: Post,
as: 'posts',
limit: 5
}]
});
// Получить пост с автором
const postWithAuthor = await Post.findByPk(1, {
include: [{
model: User,
as: 'author',
attributes: ['firstName', 'lastName']
}]
});
Миграции и изменения схемы
Допустим, нужно добавить поле isActive
в таблицу пользователей:
npx sequelize-cli migration:generate --name add-isActive-to-users
Редактируем созданную миграцию:
'use strict';
module.exports = {
async up(queryInterface, Sequelize) {
await queryInterface.addColumn('users', 'isActive', {
type: Sequelize.BOOLEAN,
defaultValue: true,
allowNull: false
});
},
async down(queryInterface, Sequelize) {
await queryInterface.removeColumn('users', 'isActive');
}
};
Применяем миграцию:
npx sequelize-cli db:migrate
Не забываем обновить модель:
// В models/user.js
isActive: {
type: DataTypes.BOOLEAN,
defaultValue: true,
allowNull: false
}
Продвинутые запросы
Sequelize поддерживает сложные запросы через операторы:
const { Op } = require('sequelize');
// Поиск активных пользователей старше 25 лет
const activeUsers = await User.findAll({
where: {
age: {
[Op.gt]: 25
},
isActive: true,
email: {
[Op.like]: '%@gmail.com'
}
}
});
// Группировка и агрегация
const userStats = await User.findAll({
attributes: [
'isActive',
[Sequelize.fn('COUNT', Sequelize.col('id')), 'count'],
[Sequelize.fn('AVG', Sequelize.col('age')), 'avgAge']
],
group: ['isActive']
});
// Сложные условия
const complexQuery = await User.findAll({
where: {
[Op.or]: [
{ age: { [Op.gt]: 30 } },
{
[Op.and]: [
{ isActive: true },
{ age: { [Op.between]: [18, 30] } }
]
}
]
}
});
Транзакции
Для критически важных операций используем транзакции:
const { sequelize } = require('./models');
async function transferPoints(fromUserId, toUserId, points) {
const transaction = await sequelize.transaction();
try {
// Списываем очки с одного пользователя
await User.decrement(
{ points: points },
{ where: { id: fromUserId }, transaction }
);
// Добавляем очки другому пользователю
await User.increment(
{ points: points },
{ where: { id: toUserId }, transaction }
);
// Создаём запись о транзакции
await Transaction.create({
fromUserId,
toUserId,
points,
type: 'transfer'
}, { transaction });
await transaction.commit();
console.log('Транзакция выполнена успешно');
} catch (error) {
await transaction.rollback();
console.error('Ошибка транзакции:', error);
throw error;
}
}
Сравнение с другими ORM
Фича | Sequelize | TypeORM | Prisma |
---|---|---|---|
Поддержка TypeScript | Частичная | Нативная | Отличная |
Миграции | Отличные | Хорошие | Автоматические |
Производительность | Средняя | Высокая | Очень высокая |
Размер bundle | Большой | Средний | Маленький |
Кривая обучения | Средняя | Крутая | Пологая |
Оптимизация производительности
Несколько важных моментов для продакшена:
- Connection pooling — настрой правильно размеры пула
- Индексы — не забывай добавлять через миграции
- Eager loading — используй
include
вместо N+1 запросов - Pagination — всегда используй
limit
иoffset
// Плохо - N+1 запрос
const users = await User.findAll();
for (const user of users) {
const posts = await user.getPosts();
console.log(posts);
}
// Хорошо - один запрос
const users = await User.findAll({
include: [{
model: Post,
as: 'posts'
}]
});
// Настройка connection pool для продакшена
const sequelize = new Sequelize('database', 'username', 'password', {
host: 'localhost',
dialect: 'mysql',
pool: {
max: 20,
min: 0,
acquire: 30000,
idle: 10000
},
logging: false
});
Полезные хуки и middleware
Sequelize позволяет навешивать хуки на различные события:
// В модели User
User.addHook('beforeCreate', async (user) => {
// Хешируем пароль перед сохранением
if (user.password) {
user.password = await bcrypt.hash(user.password, 10);
}
});
User.addHook('afterCreate', async (user) => {
// Отправляем welcome email
await sendWelcomeEmail(user.email);
});
// Глобальные хуки
sequelize.addHook('beforeBulkDestroy', (options) => {
console.log('Выполняется массовое удаление');
});
Деплой в продакшен
Для продакшена лучше использовать отдельный выделенный сервер. Создаём ecosystem.config.js
для PM2:
module.exports = {
apps: [{
name: 'sequelize-app',
script: 'app.js',
instances: 'max',
exec_mode: 'cluster',
env: {
NODE_ENV: 'production',
PORT: 3000
},
error_file: './logs/err.log',
out_file: './logs/out.log',
log_file: './logs/combined.log'
}]
};
Скрипт для деплоя:
#!/bin/bash
# deploy.sh
echo "Обновляем код..."
git pull origin main
echo "Устанавливаем зависимости..."
npm ci --production
echo "Запускаем миграции..."
NODE_ENV=production npx sequelize-cli db:migrate
echo "Перезапускаем приложение..."
pm2 reload ecosystem.config.js
Подводные камни и лучшие практики
Из личного опыта — вещи, которые могут сломать всё:
- Не используй timestamps: false в продакшене — потом будешь жалеть
- Всегда делай backup перед применением миграций
- Используй транзакции для критических операций
- Не забывай про валидацию — лучше поймать ошибку в коде, чем в базе
- Логирование в продакшене — отключай или настрой кастомное
Пример продакшен-готовой конфигурации:
// config/database.js
const config = {
development: {
username: process.env.DB_USER || 'root',
password: process.env.DB_PASS || '',
database: process.env.DB_NAME || 'sequelize_demo',
host: process.env.DB_HOST || 'localhost',
dialect: 'mysql',
logging: console.log
},
production: {
username: process.env.DB_USER,
password: process.env.DB_PASS,
database: process.env.DB_NAME,
host: process.env.DB_HOST,
dialect: 'mysql',
logging: false,
pool: {
max: 20,
min: 0,
acquire: 30000,
idle: 10000
},
retry: {
match: [
/ETIMEDOUT/,
/EHOSTUNREACH/,
/ECONNRESET/,
/ECONNREFUSED/,
/ETIMEDOUT/,
/ESOCKETTIMEDOUT/,
/EHOSTUNREACH/,
/EPIPE/,
/EAI_AGAIN/,
/SequelizeConnectionError/,
/SequelizeConnectionRefusedError/,
/SequelizeHostNotFoundError/,
/SequelizeHostNotReachableError/,
/SequelizeInvalidConnectionError/,
/SequelizeConnectionTimedOutError/
],
max: 3
}
}
};
module.exports = config;
Интеграция с другими инструментами
Sequelize отлично работает с популярными фреймворками:
- Express.js — классическая связка для REST API
- Fastify — для высокопроизводительных приложений
- NestJS — для enterprise-приложений
- GraphQL — через graphql-sequelize
Пример интеграции с Express:
const express = require('express');
const { User, Post } = require('./models');
const app = express();
app.use(express.json());
// CRUD операции
app.get('/users', async (req, res) => {
try {
const users = await User.findAll({
include: [{ model: Post, as: 'posts' }],
limit: parseInt(req.query.limit) || 10,
offset: parseInt(req.query.offset) || 0
});
res.json(users);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
app.post('/users', async (req, res) => {
try {
const user = await User.create(req.body);
res.status(201).json(user);
} catch (error) {
res.status(400).json({ error: error.message });
}
});
app.listen(3000, () => {
console.log('Сервер запущен на порту 3000');
});
Заключение
Sequelize — мощный инструмент для работы с базами данных в Node.js. Да, у неё есть свои недостатки: размер bundle’а, иногда странное поведение и не самая лучшая производительность. Но для большинства проектов это отличный выбор.
Рекомендую использовать Sequelize если:
- Работаешь с JavaScript (не TypeScript)
- Нужна стабильная, проверенная временем ORM
- Важны миграции и версионирование схемы
- Команда уже знакома с Sequelize
Для новых проектов стоит присмотреться к Prisma — она быстрее, современнее и имеет лучший DX. Но если уже есть проект на Sequelize, то нет смысла мигрировать без веских причин.
Больше информации можно найти в официальной документации: https://sequelize.org/
В этой статье собрана информация и материалы из различных интернет-источников. Мы признаем и ценим работу всех оригинальных авторов, издателей и веб-сайтов. Несмотря на то, что были приложены все усилия для надлежащего указания исходного материала, любая непреднамеренная оплошность или упущение не являются нарушением авторских прав. Все упомянутые товарные знаки, логотипы и изображения являются собственностью соответствующих владельцев. Если вы считаете, что какой-либо контент, использованный в этой статье, нарушает ваши авторские права, немедленно свяжитесь с нами для рассмотрения и принятия оперативных мер.
Данная статья предназначена исключительно для ознакомительных и образовательных целей и не ущемляет права правообладателей. Если какой-либо материал, защищенный авторским правом, был использован без должного упоминания или с нарушением законов об авторском праве, это непреднамеренно, и мы исправим это незамедлительно после уведомления. Обратите внимание, что переиздание, распространение или воспроизведение части или всего содержимого в любой форме запрещено без письменного разрешения автора и владельца веб-сайта. Для получения разрешений или дополнительных запросов, пожалуйста, свяжитесь с нами.