Skip to main content

TypeORM Subscribers

Recommendation (New Standard)

In SolidX, TypeORM subscriber hooks should always publish a background job and return quickly.

Do not execute heavy business side effects directly inside afterInsert, afterUpdate, etc.

Preferred flow:

  1. TypeORM subscriber hook runs (afterInsert, afterUpdate, ...)
  2. Hook publishes a queue message through PublisherFactory
  3. Queue subscriber processes the actual business logic asynchronously

This publisher-subscriber pair should be your default pattern for subscriber-driven side effects.

This pattern avoids common failure and consistency problems:

  • Avoids long-running work inside DB lifecycle hooks
  • Reduces risk around transaction boundaries and lock duration
  • Gives retries/failure tracking through queue infrastructure
  • Improves reliability for external side effects (email/SMS/webhooks/3rd-party APIs)
  • Keeps entity lifecycle hooks deterministic and lightweight

Anti-Pattern to Avoid

Avoid this in TypeORM subscriber hooks:

  • External API calls
  • File processing/OCR/LLM calls
  • Large cross-entity updates
  • Long computations

Do these in queue subscribers instead.

Implementation Pattern

1) Queue payload and options

import { BrokerType } from "@solidxai/core";

export interface UserChangedPayload {
userId: number;
eventType: "created" | "updated";
}

export default {
name: "userChangedQueueRabbitmq",
type: BrokerType.RabbitMQ,
queueName: "user_changed_queue",
prefetch: 5,
};

2) Publisher

import { Injectable } from "@nestjs/common";
import {
MqMessageQueueService,
MqMessageService,
QueuesModuleOptions,
RabbitMqPublisher,
} from "@solidxai/core";
import userChangedQueueOptions, { UserChangedPayload } from "./user-changed-queue-options";

@Injectable()
export class UserChangedPublisher extends RabbitMqPublisher<UserChangedPayload> {
constructor(
protected readonly mqMessageService: MqMessageService,
protected readonly mqMessageQueueService: MqMessageQueueService
) {
super(mqMessageService, mqMessageQueueService);
}

options(): QueuesModuleOptions {
return {
...userChangedQueueOptions,
};
}
}

3) TypeORM subscriber (hook only publishes via PublisherFactory)

import { Injectable } from "@nestjs/common";
import { EventSubscriber, EntitySubscriberInterface, InsertEvent, UpdateEvent } from "typeorm";
import { PublisherFactory } from "@solidxai/core";
import { User } from "../entities/user.entity";
import { UserChangedPayload } from "../background-jobs/user-changed-queue-options";

@Injectable()
@EventSubscriber()
export class UserSubscriber implements EntitySubscriberInterface<User> {
constructor(
private readonly publisherFactory: PublisherFactory<UserChangedPayload>
) {}

listenTo() {
return User;
}

async afterInsert(event: InsertEvent<User>) {
if (!event.entity?.id) return;
await this.publisherFactory.publish(
{
payload: {
userId: event.entity.id,
eventType: "created",
},
parentEntity: "user",
parentEntityId: event.entity.id,
},
"UserChangedPublisher"
);
}

async afterUpdate(event: UpdateEvent<User>) {
const id = (event.entity as User | undefined)?.id;
if (!id) return;
await this.publisherFactory.publish(
{
payload: {
userId: id,
eventType: "updated",
},
parentEntity: "user",
parentEntityId: id,
},
"UserChangedPublisher"
);
}
}

4) Queue subscriber (business logic runs here)

import { Injectable } from "@nestjs/common";
import {
MqMessageQueueService,
MqMessageService,
QueueMessage,
QueuesModuleOptions,
RabbitMqSubscriber,
} from "@solidxai/core";
import userChangedQueueOptions, { UserChangedPayload } from "./user-changed-queue-options";

@Injectable()
export class UserChangedJobSubscriber extends RabbitMqSubscriber<UserChangedPayload> {
constructor(
readonly mqMessageService: MqMessageService,
readonly mqMessageQueueService: MqMessageQueueService
) {
super(mqMessageService, mqMessageQueueService);
}

options(): QueuesModuleOptions {
return {
...userChangedQueueOptions,
};
}

async subscribe(message: QueueMessage<UserChangedPayload>) {
const { userId, eventType } = message.payload;
// Real side effects: notify, sync, call external APIs, etc.
return { success: true, userId, eventType };
}
}

Practical Rules

  • Keep TypeORM hook methods minimal and side-effect free except queue publish.
  • Publish via PublisherFactory; do not inject broker-specific publisher classes in subscriber hooks.
  • Prefer logical publisher names (UserChangedPublisher) and let the factory resolve broker-specific implementations.
  • Pass identifiers and minimal context in queue payloads.
  • Do not pass full mutable entity graphs in queue messages.
  • Keep retry/error logic in queue subscriber infrastructure.
  • Use prefetch and worker replicas to scale processing throughput.

Registration

Register TypeORM subscriber + publisher classes + queue subscriber classes as providers in your module. Publishing call sites should depend on PublisherFactory.

Notes

  • You can still use TypeORM hooks for lightweight in-memory data preparation, but not heavy side effects.
  • If in doubt, publish and process asynchronously.

See Also