Skip to Content

Lifecycle

Understanding the execution order of subscribers and operations is crucial for implementing complex business logic correctly.

Complete Execution Flow

Create Operation

1. HTTP Request arrives at controller 2. Route subscribers: onBeforeCreate (priority: high → low) 3. Controller transformers (request) 4. Controller validators 5. Service method: create() called 6. Function subscribers: onBeforeCreate (priority: high → low) 7. Repository: save() executed 8. Function subscribers: onAfterCreate (priority: low → high) 9. Service returns result 10. Controller transformers (response) 11. Route subscribers: onAfterCreate (priority: low → high) 12. HTTP Response sent to client

Update Operation

1. HTTP Request arrives at controller 2. Route subscribers: onBeforeUpdate (priority: high → low) 3. Controller transformers (request) 4. Controller validators 5. Service method: update() called 6. Function subscribers: onBeforeUpdate (priority: high → low) 7. Repository: findOne() to get existing entity 8. Repository: save() executed with updates 9. Function subscribers: onAfterUpdate (priority: low → high) 10. Service returns result 11. Controller transformers (response) 12. Route subscribers: onAfterUpdate (priority: low → high) 13. HTTP Response sent to client

Get Operation

1. HTTP Request arrives at controller 2. Route subscribers: onBeforeGet (priority: high → low) 3. Service method: get() called 4. Function subscribers: onBeforeGet (priority: high → low) 5. Repository: findOne() executed 6. Function subscribers: onAfterGet (priority: low → high) 7. Service returns result 8. Controller transformers (response) 9. Route subscribers: onAfterGet (priority: low → high) 10. HTTP Response sent to client

Get List Operation

1. HTTP Request arrives at controller 2. Route subscribers: onBeforeGetList (priority: high → low) 3. Query parameters parsed and validated 4. Service method: getList() called 5. Function subscribers: onBeforeGetList (priority: high → low) 6. Repository: find() and count() executed 7. Function subscribers: onAfterGetList (priority: low → high) 8. Service returns result with pagination metadata 9. Controller transformers (response) 10. Route subscribers: onAfterGetList (priority: low → high) 11. HTTP Response sent to client

Delete Operation

1. HTTP Request arrives at controller 2. Route subscribers: onBeforeDelete (priority: high → low) 3. Service method: delete() called 4. Function subscribers: onBeforeDelete (priority: high → low) 5. Repository: findOne() to verify existence 6. Repository: delete() or softDelete() executed 7. Function subscribers: onAfterDelete (priority: low → high) 8. Service returns result 9. Route subscribers: onAfterDelete (priority: low → high) 10. HTTP Response sent to client

Error Flow

When an error occurs at any step:

1. Error thrown at step N 2. Execution stops 3. onBeforeError hooks called (priority: high → low) - Route level (if error in controller/route subscriber) - Function level (if error in service/function subscriber) 4. onAfterError hooks called (priority: high → low) - Function level - Route level 5. Error propagated to exception filter 6. Error response sent to client

Priority Execution

Before Hooks

Execute in priority order (high to low):

// Priority 100 executes first @ApiRouteSubscriber({ entity: Post, priority: 100 }) export class SecuritySubscriber { async onBeforeCreate(context) { console.log("1. Security check"); return context.result; } } // Priority 50 executes second @ApiRouteSubscriber({ entity: Post, priority: 50 }) export class ValidationSubscriber { async onBeforeCreate(context) { console.log("2. Validation"); return context.result; } } // Priority 10 executes last @ApiRouteSubscriber({ entity: Post, priority: 10 }) export class LoggingSubscriber { async onBeforeCreate(context) { console.log("3. Logging"); return context.result; } }

After Hooks

Execute in reverse priority order (low to high):

// Priority 10 executes first @ApiRouteSubscriber({ entity: Post, priority: 10 }) export class LoggingSubscriber { async onAfterCreate(context) { console.log("1. Logging result"); return context.result; } } // Priority 50 executes second @ApiRouteSubscriber({ entity: Post, priority: 50 }) export class CacheSubscriber { async onAfterCreate(context) { console.log("2. Update cache"); return context.result; } } // Priority 100 executes last @ApiRouteSubscriber({ entity: Post, priority: 100 }) export class NotificationSubscriber { async onAfterCreate(context) { console.log("3. Send notifications"); return context.result; } }

Detailed Example with Logging

example-with-logging.ts
// Route Subscriber @Injectable() @ApiRouteSubscriber({ entity: PostEntity, priority: 50 }) export class PostRouteSubscriber extends ApiRouteSubscriberBase<PostEntity> { async onBeforeCreate(context: any): Promise<any> { console.log("[Route] onBeforeCreate - START"); return context.result; } async onAfterCreate(context: any): Promise<PostEntity> { console.log("[Route] onAfterCreate - END"); return context.result; } } // Function Subscriber @Injectable() @ApiFunctionSubscriber({ entity: PostEntity, priority: 50 }) export class PostFunctionSubscriber extends ApiFunctionSubscriberBase<PostEntity> { async onBeforeCreate(context: any): Promise<any> { console.log("[Function] onBeforeCreate - START"); return context.result; } async onAfterCreate(context: any): Promise<PostEntity> { console.log("[Function] onAfterCreate - END"); return context.result; } } // Service @Injectable() @ApiService({ entity: PostEntity }) @ApiServiceObservable() export class PostService extends ApiServiceBase<PostEntity> { async create(properties: any): Promise<PostEntity> { console.log("[Service] create - Repository operation"); return super.create(properties); } } // Console output for POST /posts: // [Route] onBeforeCreate - START // [Function] onBeforeCreate - START // [Service] create - Repository operation // [Function] onAfterCreate - END // [Route] onAfterCreate - END

Async Operations

All subscriber hooks are async and execute sequentially:

async onBeforeCreate(context: any): Promise<any> { // Operation 1 completes first await this.validateData(context.result.body); // Operation 2 completes second await this.checkPermissions(context.DATA.authenticationRequest?.user); // Operation 3 completes third await this.enrichData(context.result.body); return context.result; }

Short-Circuiting

Throw an error to stop execution:

async onBeforeCreate(context: any): Promise<any> { const user = context.DATA.authenticationRequest?.user; if (!user || user.role !== "admin") { // Stops here, error hooks are called throw new ForbiddenException("Admin access required"); } return context.result; }

Modifying Data Through Pipeline

Each subscriber can modify the result:

// Subscriber 1 (Priority 100) async onBeforeCreate(context: any): Promise<any> { context.result.body.field1 = "value1"; return context.result; // Passes to next subscriber } // Subscriber 2 (Priority 50) async onBeforeCreate(context: any): Promise<any> { // Receives modified context from Subscriber 1 context.result.body.field2 = "value2"; return context.result; // Passes to next subscriber } // Subscriber 3 (Priority 10) async onBeforeCreate(context: any): Promise<any> { // Receives context with field1 and field2 context.result.body.field3 = "value3"; return context.result; // Final result }

Transaction Boundaries

Function subscribers operate within database transactions:

async onBeforeCreate( context: IApiSubscriberFunctionExecutionContext<PostEntity, any> ): Promise<any> { const manager = context.DATA.transactionManager; if (manager) { // All operations within same transaction const tags = await manager.save(TagEntity, [...]); const category = await manager.save(CategoryEntity, {...}); context.result.body.tags = tags; context.result.body.categoryId = category.id; } return context.result; // If any operation fails, entire transaction rolls back }

Best Practices

1. Use Priority Wisely

// Validation should happen first @ApiRouteSubscriber({ entity: Post, priority: 100 }) export class ValidationSubscriber {} // Business logic in the middle @ApiRouteSubscriber({ entity: Post, priority: 50 }) export class BusinessLogicSubscriber {} // Logging and notifications last @ApiRouteSubscriber({ entity: Post, priority: 10 }) export class NotificationSubscriber {}

2. Keep Hooks Focused

// Good: One responsibility per subscriber @ApiFunctionSubscriber({ entity: Post }) export class SlugGeneratorSubscriber { async onBeforeCreate(context: any) { context.result.body.slug = this.generateSlug(context.result.body.title); return context.result; } } // Bad: Multiple responsibilities @ApiFunctionSubscriber({ entity: Post }) export class MixedSubscriber { async onBeforeCreate(context: any) { // Don't do this context.result.body.slug = this.generateSlug(context.result.body.title); await this.sendNotification(); await this.updateCache(); return context.result; } }

3. Handle Errors Gracefully

async onAfterErrorCreate( context: IApiSubscriberRouteErrorExecutionContext<PostEntity>, error: Error ): Promise<void> { try { // Cleanup operations await this.cleanup(context.ENTITY); } catch (cleanupError) { // Don't throw errors in error hooks this.logger.error("Cleanup failed", cleanupError); } }

Next Steps

Last updated on