0
# Event System & Subscribers
1
2
Comprehensive entity lifecycle event system with hooks and subscribers for implementing cross-cutting concerns like auditing, validation, and caching. TypeORM provides both decorator-based entity listeners and class-based subscribers.
3
4
## Capabilities
5
6
### Entity Lifecycle Decorators
7
8
Decorators for defining entity lifecycle event handlers directly on entity methods.
9
10
```typescript { .api }
11
/**
12
* Executed before entity is inserted into database
13
*/
14
function BeforeInsert(): MethodDecorator;
15
16
/**
17
* Executed after entity is inserted into database
18
*/
19
function AfterInsert(): MethodDecorator;
20
21
/**
22
* Executed before entity is updated in database
23
*/
24
function BeforeUpdate(): MethodDecorator;
25
26
/**
27
* Executed after entity is updated in database
28
*/
29
function AfterUpdate(): MethodDecorator;
30
31
/**
32
* Executed before entity is removed from database
33
*/
34
function BeforeRemove(): MethodDecorator;
35
36
/**
37
* Executed after entity is removed from database
38
*/
39
function AfterRemove(): MethodDecorator;
40
41
/**
42
* Executed before entity is soft removed (DeleteDateColumn set)
43
*/
44
function BeforeSoftRemove(): MethodDecorator;
45
46
/**
47
* Executed after entity is soft removed
48
*/
49
function AfterSoftRemove(): MethodDecorator;
50
51
/**
52
* Executed before soft-deleted entity is recovered
53
*/
54
function BeforeRecover(): MethodDecorator;
55
56
/**
57
* Executed after soft-deleted entity is recovered
58
*/
59
function AfterRecover(): MethodDecorator;
60
61
/**
62
* Executed after entity is loaded from database
63
*/
64
function AfterLoad(): MethodDecorator;
65
```
66
67
**Entity Lifecycle Examples:**
68
69
```typescript
70
import {
71
Entity, Column, PrimaryGeneratedColumn, CreateDateColumn, UpdateDateColumn,
72
BeforeInsert, AfterInsert, BeforeUpdate, AfterUpdate, AfterLoad
73
} from "typeorm";
74
75
@Entity()
76
export class User {
77
@PrimaryGeneratedColumn()
78
id: number;
79
80
@Column()
81
email: string;
82
83
@Column()
84
passwordHash: string;
85
86
@CreateDateColumn()
87
createdAt: Date;
88
89
@UpdateDateColumn()
90
updatedAt: Date;
91
92
@Column({ nullable: true })
93
lastLoginAt: Date;
94
95
// Computed property
96
fullName: string;
97
98
@BeforeInsert()
99
beforeInsert() {
100
console.log("About to insert user:", this.email);
101
// Hash password, validate data, etc.
102
this.email = this.email.toLowerCase();
103
}
104
105
@AfterInsert()
106
afterInsert() {
107
console.log("User inserted with ID:", this.id);
108
// Send welcome email, create audit log, etc.
109
}
110
111
@BeforeUpdate()
112
beforeUpdate() {
113
console.log("About to update user:", this.id);
114
// Validate changes, update modified fields, etc.
115
this.email = this.email.toLowerCase();
116
}
117
118
@AfterUpdate()
119
afterUpdate() {
120
console.log("User updated:", this.id);
121
// Clear cache, send notifications, etc.
122
}
123
124
@AfterLoad()
125
afterLoad() {
126
// Compute derived properties after loading from database
127
this.fullName = `${this.firstName} ${this.lastName}`;
128
}
129
}
130
```
131
132
### Entity Subscriber Interface
133
134
Interface for creating global event subscribers that can listen to events across all entities or specific entities.
135
136
```typescript { .api }
137
/**
138
* Interface for entity event subscribers
139
* @template Entity - Optional entity type to listen to
140
*/
141
interface EntitySubscriberInterface<Entity = any> {
142
/**
143
* Specifies which entity this subscriber listens to
144
* @returns Entity constructor, schema, or string name
145
*/
146
listenTo?(): ObjectType<Entity> | EntitySchema<Entity> | string;
147
148
/**
149
* Called after entity is loaded from database
150
* @param entity - Loaded entity
151
* @param event - Load event details
152
*/
153
afterLoad?(entity: Entity, event?: LoadEvent<Entity>): Promise<any> | void;
154
155
/**
156
* Called before entity is inserted
157
* @param event - Insert event details
158
*/
159
beforeInsert?(event: InsertEvent<Entity>): Promise<any> | void;
160
161
/**
162
* Called after entity is inserted
163
* @param event - Insert event details
164
*/
165
afterInsert?(event: InsertEvent<Entity>): Promise<any> | void;
166
167
/**
168
* Called before entity is updated
169
* @param event - Update event details
170
*/
171
beforeUpdate?(event: UpdateEvent<Entity>): Promise<any> | void;
172
173
/**
174
* Called after entity is updated
175
* @param event - Update event details
176
*/
177
afterUpdate?(event: UpdateEvent<Entity>): Promise<any> | void;
178
179
/**
180
* Called before entity is removed
181
* @param event - Remove event details
182
*/
183
beforeRemove?(event: RemoveEvent<Entity>): Promise<any> | void;
184
185
/**
186
* Called after entity is removed
187
* @param event - Remove event details
188
*/
189
afterRemove?(event: RemoveEvent<Entity>): Promise<any> | void;
190
191
/**
192
* Called before entity is soft removed
193
* @param event - Soft remove event details
194
*/
195
beforeSoftRemove?(event: SoftRemoveEvent<Entity>): Promise<any> | void;
196
197
/**
198
* Called after entity is soft removed
199
* @param event - Soft remove event details
200
*/
201
afterSoftRemove?(event: SoftRemoveEvent<Entity>): Promise<any> | void;
202
203
/**
204
* Called before soft-deleted entity is recovered
205
* @param event - Recover event details
206
*/
207
beforeRecover?(event: RecoverEvent<Entity>): Promise<any> | void;
208
209
/**
210
* Called after soft-deleted entity is recovered
211
* @param event - Recover event details
212
*/
213
afterRecover?(event: RecoverEvent<Entity>): Promise<any> | void;
214
215
/**
216
* Called before transaction starts
217
* @param event - Transaction start event details
218
*/
219
beforeTransactionStart?(event: TransactionStartEvent): Promise<any> | void;
220
221
/**
222
* Called after transaction starts
223
* @param event - Transaction start event details
224
*/
225
afterTransactionStart?(event: TransactionStartEvent): Promise<any> | void;
226
227
/**
228
* Called before transaction commits
229
* @param event - Transaction commit event details
230
*/
231
beforeTransactionCommit?(event: TransactionCommitEvent): Promise<any> | void;
232
233
/**
234
* Called after transaction commits
235
* @param event - Transaction commit event details
236
*/
237
afterTransactionCommit?(event: TransactionCommitEvent): Promise<any> | void;
238
239
/**
240
* Called before transaction rolls back
241
* @param event - Transaction rollback event details
242
*/
243
beforeTransactionRollback?(event: TransactionRollbackEvent): Promise<any> | void;
244
245
/**
246
* Called after transaction rolls back
247
* @param event - Transaction rollback event details
248
*/
249
afterTransactionRollback?(event: TransactionRollbackEvent): Promise<any> | void;
250
}
251
252
/**
253
* Marks a class as an entity subscriber
254
*/
255
function EventSubscriber(): ClassDecorator;
256
```
257
258
### Event Interfaces
259
260
Detailed interfaces for different types of entity events.
261
262
```typescript { .api }
263
/**
264
* Event fired when entity is loaded from database
265
*/
266
interface LoadEvent<Entity> {
267
/** Connection where event occurred */
268
connection: DataSource;
269
/** Query runner executing the operation */
270
queryRunner?: QueryRunner;
271
/** Entity manager */
272
manager: EntityManager;
273
/** Loaded entity */
274
entity: Entity;
275
}
276
277
/**
278
* Event fired during entity insert operations
279
*/
280
interface InsertEvent<Entity> {
281
/** Connection where event occurred */
282
connection: DataSource;
283
/** Query runner executing the operation */
284
queryRunner?: QueryRunner;
285
/** Entity manager */
286
manager: EntityManager;
287
/** Entity being inserted */
288
entity: Entity;
289
}
290
291
/**
292
* Event fired during entity update operations
293
*/
294
interface UpdateEvent<Entity> {
295
/** Connection where event occurred */
296
connection: DataSource;
297
/** Query runner executing the operation */
298
queryRunner?: QueryRunner;
299
/** Entity manager */
300
manager: EntityManager;
301
/** Entity being updated */
302
entity: Entity;
303
/** Database entity state (before update) */
304
databaseEntity: Entity;
305
/** Changed columns */
306
updatedColumns: ColumnMetadata[];
307
/** Changed relations */
308
updatedRelations: RelationMetadata[];
309
}
310
311
/**
312
* Event fired during entity remove operations
313
*/
314
interface RemoveEvent<Entity> {
315
/** Connection where event occurred */
316
connection: DataSource;
317
/** Query runner executing the operation */
318
queryRunner?: QueryRunner;
319
/** Entity manager */
320
manager: EntityManager;
321
/** Entity being removed */
322
entity: Entity;
323
/** Entity ID being removed */
324
entityId: any;
325
/** Database entity state */
326
databaseEntity: Entity;
327
}
328
329
/**
330
* Event fired during soft remove operations
331
*/
332
interface SoftRemoveEvent<Entity> {
333
/** Connection where event occurred */
334
connection: DataSource;
335
/** Query runner executing the operation */
336
queryRunner?: QueryRunner;
337
/** Entity manager */
338
manager: EntityManager;
339
/** Entity being soft removed */
340
entity: Entity;
341
/** Database entity state */
342
databaseEntity: Entity;
343
}
344
345
/**
346
* Event fired during entity recovery operations
347
*/
348
interface RecoverEvent<Entity> {
349
/** Connection where event occurred */
350
connection: DataSource;
351
/** Query runner executing the operation */
352
queryRunner?: QueryRunner;
353
/** Entity manager */
354
manager: EntityManager;
355
/** Entity being recovered */
356
entity: Entity;
357
/** Database entity state */
358
databaseEntity: Entity;
359
}
360
361
/**
362
* Event fired when database transaction starts
363
*/
364
interface TransactionStartEvent {
365
/** Connection where event occurred */
366
connection: DataSource;
367
/** Query runner executing the transaction */
368
queryRunner: QueryRunner;
369
}
370
371
/**
372
* Event fired when database transaction commits
373
*/
374
interface TransactionCommitEvent {
375
/** Connection where event occurred */
376
connection: DataSource;
377
/** Query runner executing the transaction */
378
queryRunner: QueryRunner;
379
}
380
381
/**
382
* Event fired when database transaction rolls back
383
*/
384
interface TransactionRollbackEvent {
385
/** Connection where event occurred */
386
connection: DataSource;
387
/** Query runner executing the transaction */
388
queryRunner: QueryRunner;
389
}
390
```
391
392
**Entity Subscriber Examples:**
393
394
```typescript
395
import { EventSubscriber, EntitySubscriberInterface, InsertEvent, UpdateEvent } from "typeorm";
396
397
// Global subscriber listening to all entities
398
@EventSubscriber()
399
export class GlobalAuditSubscriber implements EntitySubscriberInterface {
400
beforeInsert(event: InsertEvent<any>) {
401
console.log(`Inserting entity: ${event.entity.constructor.name}`);
402
// Log to audit system
403
}
404
405
beforeUpdate(event: UpdateEvent<any>) {
406
console.log(`Updating entity: ${event.entity.constructor.name}`);
407
// Compare changes and log
408
console.log("Changed columns:", event.updatedColumns.map(col => col.propertyName));
409
}
410
}
411
412
// Subscriber for specific entity
413
@EventSubscriber()
414
export class UserSubscriber implements EntitySubscriberInterface<User> {
415
listenTo() {
416
return User;
417
}
418
419
beforeInsert(event: InsertEvent<User>) {
420
// Hash password before insert
421
if (event.entity.password) {
422
event.entity.passwordHash = hashPassword(event.entity.password);
423
delete event.entity.password;
424
}
425
}
426
427
afterInsert(event: InsertEvent<User>) {
428
// Send welcome email
429
sendWelcomeEmail(event.entity.email);
430
431
// Create user profile
432
const profile = new Profile();
433
profile.user = event.entity;
434
return event.manager.save(Profile, profile);
435
}
436
437
beforeUpdate(event: UpdateEvent<User>) {
438
// Check if email changed
439
if (event.entity.email !== event.databaseEntity.email) {
440
event.entity.emailVerified = false;
441
}
442
}
443
444
afterUpdate(event: UpdateEvent<User>) {
445
// Clear user cache
446
clearUserCache(event.entity.id);
447
448
// Send notification if important field changed
449
const importantFields = ['email', 'role'];
450
const changedImportantFields = event.updatedColumns
451
.map(col => col.propertyName)
452
.filter(field => importantFields.includes(field));
453
454
if (changedImportantFields.length > 0) {
455
sendAccountChangeNotification(event.entity.email, changedImportantFields);
456
}
457
}
458
459
beforeRemove(event: RemoveEvent<User>) {
460
// Archive user data before deletion
461
archiveUserData(event.entity);
462
}
463
}
464
465
// Transaction subscriber
466
@EventSubscriber()
467
export class TransactionSubscriber implements EntitySubscriberInterface {
468
afterTransactionCommit(event: TransactionCommitEvent) {
469
// Clear cache, send events, etc.
470
console.log("Transaction committed successfully");
471
}
472
473
afterTransactionRollback(event: TransactionRollbackEvent) {
474
// Log error, send alerts, etc.
475
console.log("Transaction rolled back");
476
}
477
}
478
```
479
480
### Advanced Event Patterns
481
482
Complex event handling patterns for enterprise applications.
483
484
```typescript
485
// Event aggregation subscriber
486
@EventSubscriber()
487
export class EventAggregatorSubscriber implements EntitySubscriberInterface {
488
private events: any[] = [];
489
490
afterInsert(event: InsertEvent<any>) {
491
this.events.push({
492
type: 'INSERT',
493
entity: event.entity.constructor.name,
494
id: event.entity.id,
495
timestamp: new Date()
496
});
497
}
498
499
afterUpdate(event: UpdateEvent<any>) {
500
this.events.push({
501
type: 'UPDATE',
502
entity: event.entity.constructor.name,
503
id: event.entity.id,
504
changes: event.updatedColumns.map(col => col.propertyName),
505
timestamp: new Date()
506
});
507
}
508
509
afterTransactionCommit(event: TransactionCommitEvent) {
510
// Batch send all events after successful transaction
511
if (this.events.length > 0) {
512
sendEventBatch(this.events);
513
this.events = [];
514
}
515
}
516
517
afterTransactionRollback(event: TransactionRollbackEvent) {
518
// Clear events on rollback
519
this.events = [];
520
}
521
}
522
523
// Validation subscriber
524
@EventSubscriber()
525
export class ValidationSubscriber implements EntitySubscriberInterface {
526
beforeInsert(event: InsertEvent<any>) {
527
return this.validateEntity(event.entity, event.manager);
528
}
529
530
beforeUpdate(event: UpdateEvent<any>) {
531
return this.validateEntity(event.entity, event.manager);
532
}
533
534
private async validateEntity(entity: any, manager: EntityManager) {
535
// Custom validation logic
536
const errors = [];
537
538
// Example: Check unique constraints
539
if (entity.email) {
540
const existingUser = await manager.findOneBy(entity.constructor, {
541
email: entity.email,
542
id: Not(entity.id || 0)
543
});
544
if (existingUser) {
545
errors.push('Email already exists');
546
}
547
}
548
549
if (errors.length > 0) {
550
throw new Error(`Validation failed: ${errors.join(', ')}`);
551
}
552
}
553
}
554
555
// Caching subscriber
556
@EventSubscriber()
557
export class CacheSubscriber implements EntitySubscriberInterface {
558
afterInsert(event: InsertEvent<any>) {
559
this.invalidateCache(event.entity.constructor.name);
560
}
561
562
afterUpdate(event: UpdateEvent<any>) {
563
this.invalidateCache(event.entity.constructor.name);
564
this.invalidateEntityCache(event.entity.constructor.name, event.entity.id);
565
}
566
567
afterRemove(event: RemoveEvent<any>) {
568
this.invalidateCache(event.entity.constructor.name);
569
this.invalidateEntityCache(event.entity.constructor.name, event.entityId);
570
}
571
572
private invalidateCache(entityName: string) {
573
// Clear list caches
574
cache.del(`${entityName}:list:*`);
575
}
576
577
private invalidateEntityCache(entityName: string, entityId: any) {
578
// Clear specific entity cache
579
cache.del(`${entityName}:${entityId}`);
580
}
581
}
582
```
583
584
### Event Configuration
585
586
Configure event subscribers in your DataSource:
587
588
```typescript
589
import { DataSource } from "typeorm";
590
import { UserSubscriber, GlobalAuditSubscriber } from "./subscribers";
591
592
const dataSource = new DataSource({
593
// ... other options
594
subscribers: [
595
UserSubscriber,
596
GlobalAuditSubscriber,
597
// ... other subscribers
598
],
599
});
600
601
// Or add subscribers programmatically
602
dataSource.subscribers.push(new UserSubscriber());
603
```
604
605
## Event System Use Cases
606
607
The event system is ideal for:
608
609
- **Auditing**: Track all entity changes for compliance
610
- **Validation**: Implement complex business rules
611
- **Caching**: Manage cache invalidation
612
- **Notifications**: Send emails/messages on entity changes
613
- **Data Transformation**: Modify data before/after operations
614
- **Integration**: Sync with external systems
615
- **Logging**: Track database operations
616
- **Security**: Implement access controls and monitoring
617
618
## Best Practices
619
620
### Performance Considerations
621
622
```typescript
623
@EventSubscriber()
624
export class PerformantSubscriber implements EntitySubscriberInterface {
625
// Use async operations carefully
626
async afterInsert(event: InsertEvent<any>) {
627
// Don't block the main transaction
628
setImmediate(() => {
629
this.sendNotificationAsync(event.entity);
630
});
631
}
632
633
private async sendNotificationAsync(entity: any) {
634
// Async operation that doesn't affect main transaction
635
}
636
637
// Batch operations when possible
638
private pendingOperations: any[] = [];
639
640
afterUpdate(event: UpdateEvent<any>) {
641
this.pendingOperations.push(event);
642
}
643
644
afterTransactionCommit(event: TransactionCommitEvent) {
645
if (this.pendingOperations.length > 0) {
646
this.processBatch(this.pendingOperations);
647
this.pendingOperations = [];
648
}
649
}
650
}
651
```