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,
IApiSubscriberRouteExecutionContext
} 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: IApiSubscriberRouteExecutionContext<PostEntity, PostEntity>
): Promise<PostEntity> {
const post = context.result;
const user = context.DATA.authenticationRequest?.user;
console.log(`User ${user?.id} created post ${post.id}`);
return post;
}
}
Function Subscriber (Data Enrichment)
post-slug.subscriber.ts
import { Injectable } from "@nestjs/common";
import {
ApiFunctionSubscriber,
ApiFunctionSubscriberBase,
IApiSubscriberFunctionExecutionContext,
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: IApiSubscriberFunctionExecutionContext<
PostEntity,
TApiFunctionCreateProperties<PostEntity>
>
): Promise<TApiFunctionCreateProperties<PostEntity>> {
const { body } = context.result;
if (body.title && !body.slug) {
body.slug = slugify(body.title, { lower: true, strict: true });
}
return context.result;
}
}
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 low → high) - Result returned to controller
- Route subscribers
onAfter...
(priority low → high) - 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