Home » Как использовать Sequelize с Node.js и MySQL
Как использовать Sequelize с Node.js и MySQL

Как использовать 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/


В этой статье собрана информация и материалы из различных интернет-источников. Мы признаем и ценим работу всех оригинальных авторов, издателей и веб-сайтов. Несмотря на то, что были приложены все усилия для надлежащего указания исходного материала, любая непреднамеренная оплошность или упущение не являются нарушением авторских прав. Все упомянутые товарные знаки, логотипы и изображения являются собственностью соответствующих владельцев. Если вы считаете, что какой-либо контент, использованный в этой статье, нарушает ваши авторские права, немедленно свяжитесь с нами для рассмотрения и принятия оперативных мер.

Данная статья предназначена исключительно для ознакомительных и образовательных целей и не ущемляет права правообладателей. Если какой-либо материал, защищенный авторским правом, был использован без должного упоминания или с нарушением законов об авторском праве, это непреднамеренно, и мы исправим это незамедлительно после уведомления. Обратите внимание, что переиздание, распространение или воспроизведение части или всего содержимого в любой форме запрещено без письменного разрешения автора и владельца веб-сайта. Для получения разрешений или дополнительных запросов, пожалуйста, свяжитесь с нами.

Leave a reply

Your email address will not be published. Required fields are marked