В Sequelize хуки представляют собой функции, которые автоматически вызываются на определённых этапах жизненного цикла модели. Они позволяют вмешиваться в процесс создания, обновления, удаления и других операций с данными, не нарушая архитектуру приложения и не дублируя бизнес-логику в контроллерах.
Особое значение имеют хуки beforeCreate и
beforeUpdate, так как именно они используются для
модификации данных до их сохранения в базу.
Хуки в Sequelize работают как промежуточные обработчики событий. Они вызываются автоматически в момент выполнения определённых операций над моделью.
Основные категории:
beforeCreate и beforeUpdate относятся к
pre-hooks — они срабатывают до выполнения
SQL-запроса.
Хук beforeCreate вызывается перед созданием новой записи
в базе данных.
Model.beforeCreate(async (instance, options) => {
// логика
});
Один из самых частых кейсов — обработка пароля перед сохранением:
import bcrypt from 'bcrypt';
User.beforeCreate(async (user) => {
if (user.password) {
const salt = await bcrypt.genSalt(10);
user.password = await bcrypt.hash(user.password, salt);
}
});
В этом случае исходный пароль никогда не сохраняется в базе в открытом виде.
User.beforeCreate((user) => {
user.createdAtTimestamp = Date.now();
});
User.beforeCreate((user) => {
user.email = user.email.toLowerCase().trim();
});
Model.create()individualHooks: true)instance автоматически попадают в
INSERT-запросХук beforeUpdate вызывается перед обновлением
существующей записи.
Model.beforeUpdate(async (instance, options) => {
// логика
});
Главное отличие заключается в том, что:
beforeCreate работает только с новыми записямиbeforeUpdate работает только с уже существующимиПри этом оба хука имеют доступ к экземпляру модели и могут изменять его перед сохранением.
import bcrypt from 'bcrypt';
User.beforeUpdate(async (user) => {
if (user.changed('password')) {
const salt = await bcrypt.genSalt(10);
user.password = await bcrypt.hash(user.password, salt);
}
});
Ключевой момент — проверка changed(), чтобы избежать
повторного хеширования уже хешированного значения.
User.beforeUpdate((user) => {
user.updatedAtTimestamp = Date.now();
});
User.beforeUpdate((user) => {
if (user.status === 'banned' && !user.reason) {
throw new Error('Причина блокировки обязательна');
}
});
Если в хуке выбрасывается исключение, операция обновления отменяется.
Метод changed() используется для определения, изменилось
ли поле:
user.changed('email')
или
user.changed()
(вернёт список изменённых полей)
save() или
update()bulkUpdateПри обновлении:
beforeValidatebeforeUpdateafterUpdateПри создании:
beforeValidatebeforeCreateafterCreateЧасто хуки используются совместно с bcrypt.js для безопасного хранения паролей.
import bcrypt from 'bcrypt';
export default (sequelize, DataTypes) => {
const User = sequelize.define('User', {
email: DataTypes.STRING,
password: DataTypes.STRING
});
User.beforeCreate(async (user) => {
const salt = await bcrypt.genSalt(10);
user.password = await bcrypt.hash(user.password, salt);
});
User.beforeUpdate(async (user) => {
if (user.changed('password')) {
const salt = await bcrypt.genSalt(10);
user.password = await bcrypt.hash(user.password, salt);
}
});
return User;
};
Если не проверять changed('password'), можно случайно
зашифровать уже зашифрованное значение.
User.bulkCreate(users, { individualHooks: true });
Без этого хуки не сработают для каждого элемента.
Если используется bcrypt или другие async операции, необходимо всегда
использовать async/await.
Нельзя рассчитывать на мутацию внешних переменных — только
instance.
Sequelize позволяет отключать хуки:
User.update(
{ name: 'Test' },
{ hooks: false }
);
Это полезно для системных операций, но опасно для критичных данных.
Эти хуки позволяют:
Они часто используются как слой доменной логики, который работает независимо от контроллеров и сервисов.