0
# Hooks
1
2
Lifecycle event system for extending model and query behavior with customizable hooks that execute at specific points during database operations.
3
4
## Capabilities
5
6
### Instance Lifecycle Hooks
7
8
Hooks that execute during individual model instance operations.
9
10
```typescript { .api }
11
/**
12
* Validation hooks
13
*/
14
interface ValidationHooks {
15
/** Before instance validation */
16
beforeValidate: (instance: Model, options: ValidationOptions) => Promise<void> | void;
17
/** After successful validation */
18
afterValidate: (instance: Model, options: ValidationOptions) => Promise<void> | void;
19
/** When validation fails */
20
validationFailed: (instance: Model, options: ValidationOptions, error: ValidationError) => Promise<void> | void;
21
}
22
23
/**
24
* Create/Update/Save hooks
25
*/
26
interface CRUDHooks {
27
/** Before creating new instance */
28
beforeCreate: (instance: Model, options: CreateOptions) => Promise<void> | void;
29
/** After creating new instance */
30
afterCreate: (instance: Model, options: CreateOptions) => Promise<void> | void;
31
32
/** Before updating instance */
33
beforeUpdate: (instance: Model, options: UpdateOptions) => Promise<void> | void;
34
/** After updating instance */
35
afterUpdate: (instance: Model, options: UpdateOptions) => Promise<void> | void;
36
37
/** Before saving (create or update) */
38
beforeSave: (instance: Model, options: SaveOptions) => Promise<void> | void;
39
/** After saving (create or update) */
40
afterSave: (instance: Model, options: SaveOptions) => Promise<void> | void;
41
42
/** Before destroying instance */
43
beforeDestroy: (instance: Model, options: InstanceDestroyOptions) => Promise<void> | void;
44
/** After destroying instance */
45
afterDestroy: (instance: Model, options: InstanceDestroyOptions) => Promise<void> | void;
46
47
/** Before restoring soft-deleted instance */
48
beforeRestore: (instance: Model, options: RestoreOptions) => Promise<void> | void;
49
/** After restoring soft-deleted instance */
50
afterRestore: (instance: Model, options: RestoreOptions) => Promise<void> | void;
51
52
/** Before upserting */
53
beforeUpsert: (values: any, options: UpsertOptions) => Promise<void> | void;
54
/** After upserting */
55
afterUpsert: (result: [Model, boolean], options: UpsertOptions) => Promise<void> | void;
56
}
57
```
58
59
**Usage Examples:**
60
61
```typescript
62
// Define hooks in model definition
63
class User extends Model {}
64
User.init({
65
firstName: DataTypes.STRING,
66
lastName: DataTypes.STRING,
67
email: DataTypes.STRING,
68
hashedPassword: DataTypes.STRING,
69
lastLoginAt: DataTypes.DATE
70
}, {
71
sequelize,
72
modelName: 'user',
73
hooks: {
74
// Hash password before creating user
75
beforeCreate: async (user, options) => {
76
if (user.password) {
77
user.hashedPassword = await bcrypt.hash(user.password, 10);
78
delete user.password;
79
}
80
},
81
82
// Update login timestamp after creation
83
afterCreate: async (user, options) => {
84
console.log(`User ${user.email} created at ${new Date()}`);
85
},
86
87
// Hash password before updates too
88
beforeUpdate: async (user, options) => {
89
if (user.changed('password')) {
90
user.hashedPassword = await bcrypt.hash(user.password, 10);
91
delete user.password;
92
}
93
},
94
95
// Log validation failures
96
validationFailed: (user, options, error) => {
97
console.log(`Validation failed for user: ${error.message}`);
98
}
99
}
100
});
101
102
// Add hooks after model definition
103
User.addHook('beforeSave', async (user, options) => {
104
user.email = user.email.toLowerCase();
105
});
106
107
User.addHook('afterDestroy', (user, options) => {
108
console.log(`User ${user.email} was deleted`);
109
});
110
```
111
112
### Bulk Operation Hooks
113
114
Hooks that execute during bulk database operations.
115
116
```typescript { .api }
117
/**
118
* Bulk operation hooks
119
*/
120
interface BulkHooks {
121
/** Before bulk create operation */
122
beforeBulkCreate: (instances: Model[], options: BulkCreateOptions) => Promise<void> | void;
123
/** After bulk create operation */
124
afterBulkCreate: (instances: Model[], options: BulkCreateOptions) => Promise<void> | void;
125
126
/** Before bulk update operation */
127
beforeBulkUpdate: (options: UpdateOptions) => Promise<void> | void;
128
/** After bulk update operation */
129
afterBulkUpdate: (options: UpdateOptions) => Promise<void> | void;
130
131
/** Before bulk destroy operation */
132
beforeBulkDestroy: (options: DestroyOptions) => Promise<void> | void;
133
/** After bulk destroy operation */
134
afterBulkDestroy: (options: DestroyOptions) => Promise<void> | void;
135
136
/** Before bulk restore operation */
137
beforeBulkRestore: (options: RestoreOptions) => Promise<void> | void;
138
/** After bulk restore operation */
139
afterBulkRestore: (options: RestoreOptions) => Promise<void> | void;
140
141
/** Before bulk sync operation */
142
beforeBulkSync: (options: SyncOptions) => Promise<void> | void;
143
/** After bulk sync operation */
144
afterBulkSync: (options: SyncOptions) => Promise<void> | void;
145
}
146
```
147
148
**Usage Examples:**
149
150
```typescript
151
// Bulk operation hooks
152
User.addHook('beforeBulkCreate', (instances, options) => {
153
console.log(`About to create ${instances.length} users`);
154
155
// Modify all instances before creation
156
instances.forEach(user => {
157
user.email = user.email.toLowerCase();
158
user.createdAt = new Date();
159
});
160
});
161
162
User.addHook('afterBulkUpdate', (options) => {
163
console.log('Bulk update completed');
164
165
// Clear cache after bulk operations
166
cache.clear('users');
167
});
168
169
User.addHook('beforeBulkDestroy', (options) => {
170
// Log what's about to be deleted
171
console.log('About to delete users matching:', options.where);
172
});
173
```
174
175
### Query Hooks
176
177
Hooks that execute around database query operations.
178
179
```typescript { .api }
180
/**
181
* Query-related hooks
182
*/
183
interface QueryHooks {
184
/** Before find operations */
185
beforeFind: (options: FindOptions) => Promise<void> | void;
186
/** After find operations */
187
afterFind: (instances: Model | Model[] | null, options: FindOptions) => Promise<void> | void;
188
189
/** Before count operations */
190
beforeCount: (options: CountOptions) => Promise<void> | void;
191
192
/** Before find, after include expansion */
193
beforeFindAfterExpandIncludeAll: (options: FindOptions) => Promise<void> | void;
194
/** Before find, after options processing */
195
beforeFindAfterOptions: (options: FindOptions) => Promise<void> | void;
196
197
/** Before any SQL query execution */
198
beforeQuery: (options: QueryOptions, query: string) => Promise<void> | void;
199
/** After any SQL query execution */
200
afterQuery: (options: QueryOptions, query: string) => Promise<void> | void;
201
}
202
```
203
204
**Usage Examples:**
205
206
```typescript
207
// Query hooks for caching and logging
208
User.addHook('beforeFind', (options) => {
209
console.log('Finding users with options:', options);
210
211
// Add default ordering if not specified
212
if (!options.order) {
213
options.order = [['createdAt', 'DESC']];
214
}
215
});
216
217
User.addHook('afterFind', (result, options) => {
218
if (Array.isArray(result)) {
219
console.log(`Found ${result.length} users`);
220
} else if (result) {
221
console.log(`Found user: ${result.email}`);
222
} else {
223
console.log('No user found');
224
}
225
});
226
227
// Global query logging
228
sequelize.addHook('beforeQuery', (options, query) => {
229
console.time('query');
230
console.log('Executing query:', query);
231
});
232
233
sequelize.addHook('afterQuery', (options, query) => {
234
console.timeEnd('query');
235
});
236
```
237
238
### Model Definition Hooks
239
240
Hooks that execute during model definition and association setup.
241
242
```typescript { .api }
243
/**
244
* Model definition hooks
245
*/
246
interface ModelDefinitionHooks {
247
/** Before model definition */
248
beforeDefine: (attributes: ModelAttributes, options: DefineOptions) => Promise<void> | void;
249
/** After model definition */
250
afterDefine: (model: typeof Model) => Promise<void> | void;
251
252
/** Before model initialization */
253
beforeInit: (attributes: ModelAttributes, options: InitOptions) => Promise<void> | void;
254
/** After model initialization */
255
afterInit: (model: typeof Model) => Promise<void> | void;
256
257
/** Before association setup */
258
beforeAssociate: (model: typeof Model, associations: any) => Promise<void> | void;
259
/** After association setup */
260
afterAssociate: (model: typeof Model, associations: any) => Promise<void> | void;
261
}
262
```
263
264
**Usage Examples:**
265
266
```typescript
267
// Model definition hooks
268
sequelize.addHook('beforeDefine', (attributes, options) => {
269
// Automatically add timestamps if not present
270
if (!options.timestamps && !attributes.createdAt) {
271
attributes.createdAt = DataTypes.DATE;
272
attributes.updatedAt = DataTypes.DATE;
273
options.timestamps = true;
274
}
275
});
276
277
sequelize.addHook('afterDefine', (model) => {
278
console.log(`Model ${model.name} defined with attributes:`, Object.keys(model.getAttributes()));
279
});
280
```
281
282
### Connection and Sync Hooks
283
284
Hooks that execute during database connection and synchronization operations.
285
286
```typescript { .api }
287
/**
288
* Connection and sync hooks
289
*/
290
interface ConnectionSyncHooks {
291
/** Before database connection */
292
beforeConnect: (config: any) => Promise<void> | void;
293
/** After database connection */
294
afterConnect: (connection: any, config: any) => Promise<void> | void;
295
296
/** Before connection disconnect */
297
beforeDisconnect: (connection: any) => Promise<void> | void;
298
/** After connection disconnect */
299
afterDisconnect: (connection: any) => Promise<void> | void;
300
301
/** Before connection pool acquire */
302
beforePoolAcquire: (config: any) => Promise<void> | void;
303
/** After connection pool acquire */
304
afterPoolAcquire: (connection: any, config: any) => Promise<void> | void;
305
306
/** Before database sync */
307
beforeSync: (options: SyncOptions) => Promise<void> | void;
308
/** After database sync */
309
afterSync: (options: SyncOptions) => Promise<void> | void;
310
}
311
```
312
313
**Usage Examples:**
314
315
```typescript
316
// Connection hooks
317
sequelize.addHook('beforeConnect', (config) => {
318
console.log('Attempting to connect to database:', config.database);
319
});
320
321
sequelize.addHook('afterConnect', (connection, config) => {
322
console.log('Successfully connected to database');
323
});
324
325
sequelize.addHook('beforeSync', (options) => {
326
console.log('Starting database synchronization');
327
});
328
329
sequelize.addHook('afterSync', (options) => {
330
console.log('Database synchronization completed');
331
});
332
```
333
334
## Hook Management
335
336
### Adding and Removing Hooks
337
338
Methods for managing hooks dynamically.
339
340
```typescript { .api }
341
/**
342
* Add hook to model or sequelize instance
343
* @param hookType - Type of hook
344
* @param name - Hook name (optional)
345
* @param fn - Hook function
346
*/
347
addHook(hookType: string, name: string, fn: Function): void;
348
addHook(hookType: string, fn: Function): void;
349
350
/**
351
* Remove hook from model or sequelize instance
352
* @param hookType - Type of hook
353
* @param name - Hook name
354
*/
355
removeHook(hookType: string, name: string): boolean;
356
357
/**
358
* Check if hook exists
359
* @param hookType - Type of hook
360
* @param name - Hook name
361
*/
362
hasHook(hookType: string, name?: string): boolean;
363
364
/**
365
* Run hooks manually
366
* @param hookType - Type of hook
367
* @param args - Arguments to pass to hooks
368
*/
369
runHooks(hookType: string, ...args: any[]): Promise<void>;
370
```
371
372
**Usage Examples:**
373
374
```typescript
375
// Named hooks can be removed later
376
User.addHook('beforeCreate', 'hashPassword', async (user, options) => {
377
user.hashedPassword = await bcrypt.hash(user.password, 10);
378
});
379
380
User.addHook('beforeCreate', 'setDefaults', (user, options) => {
381
user.isActive = user.isActive !== false;
382
user.role = user.role || 'user';
383
});
384
385
// Remove specific hook
386
User.removeHook('beforeCreate', 'hashPassword');
387
388
// Check if hook exists
389
if (User.hasHook('beforeCreate', 'setDefaults')) {
390
console.log('setDefaults hook is active');
391
}
392
393
// Run hooks manually
394
await User.runHooks('beforeCreate', userInstance, options);
395
```
396
397
### Hook Options and Context
398
399
Advanced hook configuration and context access.
400
401
```typescript { .api }
402
/**
403
* Hook function signature with context
404
*/
405
type HookFunction<T = any> = (
406
this: typeof Model | Model,
407
...args: any[]
408
) => Promise<void> | void;
409
410
/**
411
* Hook options
412
*/
413
interface HookOptions {
414
/** Hook priority (higher runs first) */
415
priority?: number;
416
/** Hook context */
417
context?: any;
418
}
419
```
420
421
**Usage Examples:**
422
423
```typescript
424
// Hook with priority (higher priority runs first)
425
User.addHook('beforeCreate', 'validation', async function(user, options) {
426
// Validation logic
427
}, { priority: 100 });
428
429
User.addHook('beforeCreate', 'defaults', function(user, options) {
430
// Set defaults (runs after validation)
431
}, { priority: 50 });
432
433
// Access model context in hook
434
User.addHook('afterFind', function(result, options) {
435
// 'this' refers to the User model
436
console.log(`Query executed on ${this.name} model`);
437
});
438
439
// Instance method with hook context
440
User.addHook('beforeSave', function(user, options) {
441
// 'this' refers to the model class
442
console.log(`Saving ${this.name} instance`);
443
});
444
```
445
446
## Common Hook Patterns
447
448
### Audit Trail Pattern
449
450
Automatic tracking of data changes.
451
452
```typescript { .api }
453
// Audit trail implementation
454
class AuditLog extends Model {}
455
AuditLog.init({
456
tableName: DataTypes.STRING,
457
recordId: DataTypes.INTEGER,
458
action: DataTypes.ENUM('CREATE', 'UPDATE', 'DELETE'),
459
oldValues: DataTypes.JSON,
460
newValues: DataTypes.JSON,
461
userId: DataTypes.INTEGER,
462
timestamp: DataTypes.DATE
463
}, { sequelize, modelName: 'auditLog' });
464
465
// Add audit hooks to User model
466
User.addHook('afterCreate', async (user, options) => {
467
await AuditLog.create({
468
tableName: 'users',
469
recordId: user.id,
470
action: 'CREATE',
471
newValues: user.toJSON(),
472
userId: options.userId,
473
timestamp: new Date()
474
});
475
});
476
477
User.addHook('afterUpdate', async (user, options) => {
478
await AuditLog.create({
479
tableName: 'users',
480
recordId: user.id,
481
action: 'UPDATE',
482
oldValues: user._previousDataValues,
483
newValues: user.dataValues,
484
userId: options.userId,
485
timestamp: new Date()
486
});
487
});
488
489
User.addHook('afterDestroy', async (user, options) => {
490
await AuditLog.create({
491
tableName: 'users',
492
recordId: user.id,
493
action: 'DELETE',
494
oldValues: user.toJSON(),
495
userId: options.userId,
496
timestamp: new Date()
497
});
498
});
499
```
500
501
### Caching Pattern
502
503
Automatic cache invalidation.
504
505
```typescript { .api }
506
// Cache management hooks
507
const cache = new Map();
508
509
User.addHook('afterCreate', (user, options) => {
510
// Invalidate user list cache
511
cache.delete('users:list');
512
cache.delete(`users:${user.id}`);
513
});
514
515
User.addHook('afterUpdate', (user, options) => {
516
// Invalidate specific user cache
517
cache.delete(`users:${user.id}`);
518
cache.delete('users:list');
519
});
520
521
User.addHook('afterDestroy', (user, options) => {
522
// Remove from cache
523
cache.delete(`users:${user.id}`);
524
cache.delete('users:list');
525
});
526
527
User.addHook('afterFind', (result, options) => {
528
// Cache found results
529
if (Array.isArray(result)) {
530
cache.set('users:list', result);
531
} else if (result) {
532
cache.set(`users:${result.id}`, result);
533
}
534
});
535
```
536
537
### Validation and Sanitization Pattern
538
539
Data cleaning and validation.
540
541
```typescript { .api }
542
// Comprehensive validation and sanitization
543
User.addHook('beforeValidate', (user, options) => {
544
// Trim whitespace
545
if (user.firstName) user.firstName = user.firstName.trim();
546
if (user.lastName) user.lastName = user.lastName.trim();
547
if (user.email) user.email = user.email.trim().toLowerCase();
548
549
// Remove extra spaces
550
if (user.bio) user.bio = user.bio.replace(/\s+/g, ' ').trim();
551
});
552
553
User.addHook('beforeSave', async (user, options) => {
554
// Encrypt sensitive data
555
if (user.changed('socialSecurityNumber')) {
556
user.socialSecurityNumber = await encrypt(user.socialSecurityNumber);
557
}
558
559
// Generate slugs
560
if (user.changed('firstName') || user.changed('lastName')) {
561
user.slug = slugify(`${user.firstName}-${user.lastName}`);
562
}
563
});
564
```
565
566
### Notification Pattern
567
568
Event-driven notifications.
569
570
```typescript { .api }
571
// Notification system
572
User.addHook('afterCreate', async (user, options) => {
573
// Send welcome email
574
await emailService.sendWelcomeEmail(user.email, {
575
firstName: user.firstName
576
});
577
578
// Create notification
579
await Notification.create({
580
userId: user.id,
581
type: 'welcome',
582
message: 'Welcome to our platform!',
583
isRead: false
584
});
585
});
586
587
User.addHook('afterUpdate', async (user, options) => {
588
// Notify on important changes
589
if (user.changed('email')) {
590
await emailService.sendEmailChangeNotification(
591
user.previous('email'),
592
user.email
593
);
594
}
595
596
if (user.changed('password')) {
597
await emailService.sendPasswordChangeNotification(user.email);
598
}
599
});
600
```