Sling Academy
Home/Node.js/Using afterDelete and afterDestroy Hooks in Sequelize.js

Using afterDelete and afterDestroy Hooks in Sequelize.js

Last updated: December 29, 2023

Introduction

Sequelize.js is a powerful Node.js ORM for relational databases such as PostgreSQL, MySQL, SQLite, and MSSQL. It enables developers to handle common database operations with ease using JavaScript. A significant feature of Sequelize is its use of “hooks,” which are functions that are automatically run at various stages of the database query lifecycle. In this article, we explore the afterDelete and afterDestroy hooks in Sequelize, including how to use them for applying business logic following the deletion of records.

Understanding Sequelize Hooks

Hooks, also known as lifecycle events, are functions you can insert into different phases of a Sequelize model’s lifecycle. These can be used for tasks like hashing passwords before user creation, logging information, or cleaning up related records after a delete operation. The hooks we focus on here, afterDelete and afterDestroy, are called after an instance is deleted.

Setting Up Your Environment

Before diving into the specifics of hooks, ensure that Sequelize is installed and properly set up in your Node.js project. Additionally, carefully model your data according to the needs of your application.

// Sample model definition
module.exports = (sequelize, DataTypes) => {
  const User = sequelize.define('User', {
    username: DataTypes.STRING,
    email: DataTypes.STRING
    // Other attributes...,
  });
  return User;
};

afterDelete Hook

The afterDelete hook is executed after a delete operation is performed on a model instance via the destroy method. It is important for executing any additional business logic or maintenance tasks that need to occur after the delete operation, such as logging or updating a related cache.

User.afterDelete((instance, options) => {
  console.log('Record deleted:', instance.id);
});

afterDestroy Hook

The afterDestroy hook behaves similarly to afterDelete, triggering after an instance is removed from the database. The difference between afterDelete and afterDestroy traditionally varies between Sequelize versions. However, in recent versions of Sequelize, both are synonymous and can be used interchangeably.

User.afterDestroy((instance, options) => {
  console.log('User destroyed:', instance.id);
});

Using Hooks with Cascading Deletes

In a scenario where related records must also be deleted upon the destruction of a record, cascading deletes are utilized. Sequelize’s hooks are also invoked for related data if cascading is configured.

ChildModel.belongsTo(ParentModel);
ParentModel.hasMany(ChildModel, {
  onDelete: 'CASCADE',
  hooks: true
});

ParentModel.afterDestroy(async (parent, options) => {
  // Normally, associated `ChildModel` should be deleted by CASCADE.
  // The hook is necessary for additional logic after the associated models are removed.
});

Transactional Deletes

When multiple instances or related records need to be deleted as part of a single database transaction, Sequelize provides support for this common pattern. Hooks respect transactions and can be used as part of larger or atomic operations.

sequelize.transaction(async (t) => {
  const options = { transaction: t };
  const parent = await ParentModel.findByPk(1);
  await parent.destroy(options);
  // Hooks with transaction support can accomplish related cleanup tasks
});

Advanced Scenarios

For more complex use cases involving conditional logic, asynchronous operations, or error handling, hooks can be carefully designed to fit your requirements:

User.afterDestroy(async (instance, options) => {
  if (instance.isLastAdmin()) {
    throw new Error('Cannot delete last admin user.');
  }
  // Perform asynchronous cleanup.
  await doAsyncCleanup();
}, {
  type: 'after',
  name: 'preventLastAdminDeletion'
});

Conclusion

In summary, Sequelize’s afterDelete and afterDestroy hooks offer a powerful way to execute logic immediately following a model instance’s deletion. When used correctly, they can bring indispensable benefits to maintaining data integrity, enforcing business rules, and executing related background tasks post-deletion. Understanding and appropriately leveraging hooks can significantly improve the reliability and consistency of database operations within your Sequelize-based applications.

Next Article: How to Use Hooks in Sequelize.js

Previous Article: Composite Primary Key in Sequelize.js: Examples & Use Cases

Series: Sequelize.js Tutorials

Node.js

You May Also Like

  • NestJS: How to create cursor-based pagination (2 examples)
  • Cursor-Based Pagination in SequelizeJS: Practical Examples
  • MongooseJS: Cursor-Based Pagination Examples
  • Node.js: How to get location from IP address (3 approaches)
  • SequelizeJS: How to reset auto-increment ID after deleting records
  • SequelizeJS: Grouping Results by Multiple Columns
  • NestJS: Using Faker.js to populate database (for testing)
  • NodeJS: Search and download images by keyword from Unsplash API
  • NestJS: Generate N random users using Faker.js
  • Sequelize Upsert: How to insert or update a record in one query
  • NodeJS: Declaring types when using dotenv with TypeScript
  • Using ExpressJS and Multer with TypeScript
  • NodeJS: Link to static assets (JS, CSS) in Pug templates
  • NodeJS: How to use mixins in Pug templates
  • NodeJS: Displaying images and links in Pug templates
  • ExpressJS + Pug: How to use loops to render array data
  • ExpressJS: Using MORGAN to Log HTTP Requests
  • NodeJS: Using express-fileupload to simply upload files
  • ExpressJS: How to render JSON in Pug templates