Skip to Content
DocsNestJS CRUD AutomatorSubscriber System

Subscriber System

The Subscriber System is the most powerful feature of NestJS CRUD Automator for extending and customizing CRUD operations. It allows you to intercept operations at two levels: controller (route) and service (function) level.

Overview

Subscribers act as hooks that execute before, after, or on error during CRUD operations. They provide access to the execution context, allowing you to:

  • Audit operations
  • Send notifications
  • Perform complex validation
  • Enrich data before saving
  • Transform data after retrieval
  • Handle errors gracefully

Two Levels of Interception

Route Subscribers (Controller Level)

Intercept at the HTTP layer with access to:

  • Request headers
  • Client IP address
  • Authenticated user
  • Full HTTP context

Best for:

  • Auditing who performed an action
  • IP-based restrictions
  • User-specific logic
  • HTTP-specific operations

Function Subscribers (Service Level)

Intercept at the database layer before and after repository methods:

  • Pre-process data before saving
  • Post-process data after retrieval
  • Database-level validations
  • Data enrichment

Best for:

  • Data transformation
  • Computed fields
  • Database-level logic
  • Cross-entity operations

Setup Requirements

Three mandatory steps to enable the subscriber system:

1. Import ApiSubscriberModule

app.module.ts
import { ApiSubscriberModule } from "@elsikora/nestjs-crud-automator"; @Module({ imports: [ // ... other modules ApiSubscriberModule, // Required for subscriber discovery ], }) export class AppModule {}

2. Make Controller Observable

post.controller.ts
import { ApiController, ApiControllerObservable } from "@elsikora/nestjs-crud-automator"; @Controller("posts") @ApiController({ /* ... */ }) @ApiControllerObservable() // Required for route subscribers export class PostController { constructor(public service: PostService) {} }

3. Make Service Observable

post.service.ts
import { ApiService, ApiServiceBase, ApiServiceObservable } from "@elsikora/nestjs-crud-automator"; @Injectable() @ApiService({ /* ... */ }) @ApiServiceObservable() // Required for function subscribers export class PostService extends ApiServiceBase<PostEntity> { constructor( @InjectRepository(PostEntity) public repository: Repository<PostEntity>, ) { super(); } }

Quick Example

Route Subscriber (Auditing)

post-audit.subscriber.ts
import { Injectable } from "@nestjs/common"; import { ApiRouteSubscriber, ApiRouteSubscriberBase, TApiSubscriberRouteAfterCreateContext } from "@elsikora/nestjs-crud-automator"; import { PostEntity } from "./post.entity"; @Injectable() @ApiRouteSubscriber({ entity: PostEntity, priority: 10 }) export class PostAuditSubscriber extends ApiRouteSubscriberBase<PostEntity> { async onAfterCreate(context: TApiSubscriberRouteAfterCreateContext<PostEntity>): Promise<PostEntity> { const post = context.result; // Fully typed access to authentication and request data const user = context.DATA.authenticationRequest?.user; const clientIp = context.DATA.ip; console.log(`User ${user?.id} created post ${post.id} from ${clientIp}`); return post; } }

Function Subscriber (Data Enrichment)

post-slug.subscriber.ts
import { Injectable } from "@nestjs/common"; import { ApiFunctionSubscriber, ApiFunctionSubscriberBase, TApiSubscriberFunctionBeforeCreateContext, TApiFunctionCreateProperties } from "@elsikora/nestjs-crud-automator"; import { PostEntity } from "./post.entity"; import slugify from "slugify"; @Injectable() @ApiFunctionSubscriber({ entity: PostEntity }) export class PostSlugSubscriber extends ApiFunctionSubscriberBase<PostEntity> { async onBeforeCreate(context: TApiSubscriberFunctionBeforeCreateContext<PostEntity>): Promise<TApiFunctionCreateProperties<PostEntity>> { // Fully typed access to repository and transaction manager const manager = context.DATA.eventManager; const repository = context.DATA.repository; if (context.result.title && !context.result.slug) { context.result.slug = slugify(context.result.title, { lower: true, strict: true }); } return context.result; } }

Key Benefits of Helper Types

  • Simplified Syntax: Only one generic parameter needed (Entity)
  • Full Type Safety: Automatic type inference for DATA, ENTITY, and result
  • Better Autocomplete: IDE provides accurate suggestions
  • Less Boilerplate: No need to manually specify Result and Input types

Register Subscribers

post.module.ts
@Module({ imports: [TypeOrmModule.forFeature([PostEntity])], providers: [PostService, PostAuditSubscriber, PostSlugSubscriber], controllers: [PostController], }) export class PostModule {}

Execution Order

Understanding the execution order is critical:

  1. Incoming Request
  2. Route subscribers onBefore... (priority high → low)
  3. Controller logic (transformers, validators)
  4. Service method called
  5. Function subscribers onBefore... (priority high → low)
  6. Repository operation
  7. Function subscribers onAfter... (priority high → low)
  8. Result returned to controller
  9. Route subscribers onAfter... (priority high → low)
  10. Response sent to client

If an error occurs, execution stops and error hooks are called.

Next Steps

Last updated on