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:
- Incoming Request
- Route subscribers
onBefore...(priority high → low) - Controller logic (transformers, validators)
- Service method called
- Function subscribers
onBefore...(priority high → low) - Repository operation
- Function subscribers
onAfter...(priority high → low) - Result returned to controller
- Route subscribers
onAfter...(priority high → low) - Response sent to client
If an error occurs, execution stops and error hooks are called.
Next Steps
- Route Subscribers: Controller-level hooks
- Function Subscribers: Service-level hooks
- Execution Context: Context interfaces
- Lifecycle: Detailed execution flow
Last updated on