Hooks

Hooks (also known as lifecycle events), are functions which are called before and after calls in sequelize are executed. For example, if you want to always set a value on a model before saving it, you can add a beforeUpdate hook.

For a full list of hooks, see Hooks file.

Order of Operations

  1. (1)
  2. beforeBulkCreate(instances, options)
  3. beforeBulkDestroy(options)
  4. beforeBulkUpdate(options)
  5. (2)
  6. beforeValidate(instance, options)
  7. (-)
  8. validate
  9. (3)
  10. afterValidate(instance, options)
  11. - or -
  12. validationFailed(instance, options, error)
  13. (4)
  14. beforeCreate(instance, options)
  15. beforeDestroy(instance, options)
  16. beforeUpdate(instance, options)
  17. beforeSave(instance, options)
  18. beforeUpsert(values, options)
  19. (-)
  20. create
  21. destroy
  22. update
  23. (5)
  24. afterCreate(instance, options)
  25. afterDestroy(instance, options)
  26. afterUpdate(instance, options)
  27. afterSave(instance, options)
  28. afterUpsert(created, options)
  29. (6)
  30. afterBulkCreate(instances, options)
  31. afterBulkDestroy(options)
  32. afterBulkUpdate(options)

Declaring Hooks

Arguments to hooks are passed by reference. This means, that you can change the values, and this will be reflected in the insert / update statement. A hook may contain async actions - in this case the hook function should return a promise.

There are currently three ways to programmatically add hooks:

  1. // Method 1 via the .define() method
  2. const User = sequelize.define('user', {
  3. username: DataTypes.STRING,
  4. mood: {
  5. type: DataTypes.ENUM,
  6. values: ['happy', 'sad', 'neutral']
  7. }
  8. }, {
  9. hooks: {
  10. beforeValidate: (user, options) => {
  11. user.mood = 'happy';
  12. },
  13. afterValidate: (user, options) => {
  14. user.username = 'Toni';
  15. }
  16. }
  17. });
  18. // Method 2 via the .hook() method (or its alias .addHook() method)
  19. User.hook('beforeValidate', (user, options) => {
  20. user.mood = 'happy';
  21. });
  22. User.addHook('afterValidate', 'someCustomName', (user, options) => {
  23. return sequelize.Promise.reject(new Error("I'm afraid I can't let you do that!"));
  24. });
  25. // Method 3 via the direct method
  26. User.beforeCreate((user, options) => {
  27. return hashPassword(user.password).then(hashedPw => {
  28. user.password = hashedPw;
  29. });
  30. });
  31. User.afterValidate('myHookAfter', (user, options) => {
  32. user.username = 'Toni';
  33. });

Removing hooks

Only a hook with name param can be removed.

  1. const Book = sequelize.define('book', {
  2. title: DataTypes.STRING
  3. });
  4. Book.addHook('afterCreate', 'notifyUsers', (book, options) => {
  5. // ...
  6. });
  7. Book.removeHook('afterCreate', 'notifyUsers');

You can have many hooks with same name. Calling .removeHook() will remove all of them.

Global / universal hooks

Global hooks are hooks which are run for all models. They can define behaviours that you want for all your models, and are especially useful for plugins. They can be defined in two ways, which have slightly different semantics:

Sequelize.options.define (default hook)

  1. const sequelize = new Sequelize(..., {
  2. define: {
  3. hooks: {
  4. beforeCreate: () => {
  5. // Do stuff
  6. }
  7. }
  8. }
  9. });

This adds a default hook to all models, which is run if the model does not define its own beforeCreate hook:

  1. const User = sequelize.define('user');
  2. const Project = sequelize.define('project', {}, {
  3. hooks: {
  4. beforeCreate: () => {
  5. // Do other stuff
  6. }
  7. }
  8. });
  9. User.create() // Runs the global hook
  10. Project.create() // Runs its own hook (because the global hook is overwritten)

Sequelize.addHook (permanent hook)

  1. sequelize.addHook('beforeCreate', () => {
  2. // Do stuff
  3. });

This hooks is always run before create, regardless of whether the model specifies its own beforeCreate hook:

  1. const User = sequelize.define('user');
  2. const Project = sequelize.define('project', {}, {
  3. hooks: {
  4. beforeCreate: () => {
  5. // Do other stuff
  6. }
  7. }
  8. });
  9. User.create() // Runs the global hook
  10. Project.create() // Runs its own hook, followed by the global hook

Local hooks are always run before global hooks.

Instance hooks

The following hooks will emit whenever you're editing a single object

  1. beforeValidate
  2. afterValidate or validationFailed
  3. beforeCreate / beforeUpdate / beforeDestroy
  4. afterCreate / afterUpdate / afterDestroy
  1. // ...define ...
  2. User.beforeCreate(user => {
  3. if (user.accessLevel > 10 && user.username !== "Boss") {
  4. throw new Error("You can't grant this user an access level above 10!")
  5. }
  6. })

This example will return an error:

  1. User.create({username: 'Not a Boss', accessLevel: 20}).catch(err => {
  2. console.log(err); // You can't grant this user an access level above 10!
  3. });

The following example would return successful:

  1. User.create({username: 'Boss', accessLevel: 20}).then(user => {
  2. console.log(user); // user object with username as Boss and accessLevel of 20
  3. });

Model hooks

Sometimes you'll be editing more than one record at a time by utilizing the bulkCreate, update, destroy methods on the model. The following will emit whenever you're using one of those methods:

  1. beforeBulkCreate(instances, options)
  2. beforeBulkUpdate(options)
  3. beforeBulkDestroy(options)
  4. afterBulkCreate(instances, options)
  5. afterBulkUpdate(options)
  6. afterBulkDestroy(options)

If you want to emit hooks for each individual record, along with the bulk hooks you can pass individualHooks: true to the call.

  1. Model.destroy({ where: {accessLevel: 0}, individualHooks: true});
  2. // Will select all records that are about to be deleted and emit before- + after- Destroy on each instance
  3. Model.update({username: 'Toni'}, { where: {accessLevel: 0}, individualHooks: true});
  4. // Will select all records that are about to be updated and emit before- + after- Update on each instance

The options argument of hook method would be the second argument provided to the corresponding method or itscloned and extended version.

  1. Model.beforeBulkCreate((records, {fields}) => {
  2. // records = the first argument sent to .bulkCreate
  3. // fields = one of the second argument fields sent to .bulkCreate
  4. })
  5. Model.bulkCreate([
  6. {username: 'Toni'}, // part of records argument
  7. {username: 'Tobi'} // part of records argument
  8. ], {fields: ['username']} // options parameter
  9. )
  10. Model.beforeBulkUpdate(({attributes, where}) => {
  11. // where - in one of the fields of the clone of second argument sent to .update
  12. // attributes - is one of the fields that the clone of second argument of .update would be extended with
  13. })
  14. Model.update({gender: 'Male'} /*attributes argument*/, { where: {username: 'Tom'}} /*where argument*/)
  15. Model.beforeBulkDestroy(({where, individualHooks}) => {
  16. // individualHooks - default of overridden value of extended clone of second argument sent to Model.destroy
  17. // where - in one of the fields of the clone of second argument sent to Model.destroy
  18. })
  19. Model.destroy({ where: {username: 'Tom'}} /*where argument*/)

If you use Model.bulkCreate(…) with the updatesOnDuplicate option, changes made in the hook to fields that aren't given in the updatesOnDuplicate array will not be persisted to the database. However it is possible to change the updatesOnDuplicate option inside the hook if this is what you want.

  1. // Bulk updating existing users with updatesOnDuplicate option
  2. Users.bulkCreate([
  3. { id: 1, isMember: true },
  4. { id: 2, isMember: false }
  5. ], {
  6. updatesOnDuplicate: ['isMember']
  7. });
  8. User.beforeBulkCreate((users, options) => {
  9. for (const user of users) {
  10. if (user.isMember) {
  11. user.memberSince = new Date();
  12. }
  13. }
  14. // Add memberSince to updatesOnDuplicate otherwise the memberSince date wont be
  15. // saved to the database
  16. options.updatesOnDuplicate.push('memberSince');
  17. });

Associations

For the most part hooks will work the same for instances when being associated except a few things

  • When using add/set functions the beforeUpdate/afterUpdate hooks will run.
  • The only way to call beforeDestroy/afterDestroy hooks are on associations with onDelete: 'cascade' and the option hooks: true. For instance:
  1. const Projects = sequelize.define('projects', {
  2. title: DataTypes.STRING
  3. });
  4. const Tasks = sequelize.define('tasks', {
  5. title: DataTypes.STRING
  6. });
  7. Projects.hasMany(Tasks, { onDelete: 'cascade', hooks: true });
  8. Tasks.belongsTo(Projects);

This code will run beforeDestroy/afterDestroy on the Tasks table. Sequelize, by default, will try to optimize your queries as much as possible. When calling cascade on delete, Sequelize will simply execute a

  1. DELETE FROM `table` WHERE associatedIdentifier = associatedIdentifier.primaryKey

However, adding hooks: true explicitly tells Sequelize that optimization is not of your concern and will perform a SELECT on the associated objects and destroy each instance one by one in order to be able to call the hooks with the right parameters.

If your association is of type n:m, you may be interested in firing hooks on the through model when using the remove call. Internally, sequelize is using Model.destroy resulting in calling the bulkDestroy instead of the before/afterDestroy hooks on each through instance.

This can be simply solved by passing {individualHooks: true} to the remove call, resulting on each hook to be called on each removed through instance object.

A Note About Transactions

Note that many model operations in Sequelize allow you to specify a transaction in the options parameter of the method. If a transaction is specified in the original call, it will be present in the options parameter passed to the hook function. For example, consider the following snippet:

  1. // Here we use the promise-style of async hooks rather than
  2. // the callback.
  3. User.hook('afterCreate', (user, options) => {
  4. // 'transaction' will be available in options.transaction
  5. // This operation will be part of the same transaction as the
  6. // original User.create call.
  7. return User.update({
  8. mood: 'sad'
  9. }, {
  10. where: {
  11. id: user.id
  12. },
  13. transaction: options.transaction
  14. });
  15. });
  16. sequelize.transaction(transaction => {
  17. User.create({
  18. username: 'someguy',
  19. mood: 'happy',
  20. transaction
  21. });
  22. });

If we had not included the transaction option in our call to User.update in the preceding code, no change would have occurred, since our newly created user does not exist in the database until the pending transaction has been committed.

Internal Transactions

It is very important to recognize that sequelize may make use of transactions internally for certain operations such as Model.findOrCreate. If your hook functions execute read or write operations that rely on the object's presence in the database, or modify the object's stored values like the example in the preceding section, you should always specify { transaction: options.transaction }.

If the hook has been called in the process of a transacted operation, this makes sure that your dependent read/write is a part of that same transaction. If the hook is not transacted, you have simply specified { transaction: null } and can expect the default behaviour.