Stackademic

Stackademic is a learning hub for programmers, devs, coders, and engineers. Our goal is to…

Follow publication

Entity Listeners & Subscribers of TypeORM

TypeORM is a powerful Object-Relational Mapping (ORM) library for TypeScript and JavaScript that simplifies database interactions by allowing you to work with entities and relationships using familiar object-oriented programming techniques. It has gained popularity among developers due to its ease of use, robust feature set, and widespread adoption in building modern, scalable applications.

One of the best features that TypeORM provides to developers is Entity Listeners & Subscribers feature that allows more flexibility to manage and seperate complexity of the application.

What are Entity Listeners & Subscribers?

Entity Listeners & Subscribers in TypeORM provide powerful mechanisms for reacting to database events and executing custom logic.

Entity listeners are methods defined within an entity class that are automatically triggered when specific lifecycle events occur, such as beforeInsert, afterUpdate, or beforeRemove. These listeners are bound to a specific entity and are a convenient way to encapsulate business logic related to that entity.

On the other hand, subscribers in TypeORM are standalone classes that can subscribe to multiple entities and handle various events across those entities. Subscribers offer more flexibility and modularity, allowing developers to centralize event handling logic and organize it based on specific business needs or functional areas.

Both entity listeners and subscribers play crucial roles in implementing robust and maintainable database event handling in TypeORM applications, enabling developers to effectively manage database interactions and enforce application-specific behaviors.

Difference between Entity Listeners & Subscribers?

The main difference stands as Entity listeners are methods defined within an entity class and are tightly coupled to specific entities, while subscribers are standalone classes that can subscribe to multiple entities and offer more flexibility and modularity in event handling.

So both of them serve for the same purpose but they differ on how they are implemented and used within application.

As Entity Listeners are more entity specific, entity subscribers support multiple entities.

What’s behind Entity Listeners & Subscribers, how do they actually work?

Both of these are event-based implementations within applications that listen and subscribe to database events and queries to perform operations when a lifecycle event occurs, such as “Before Inserting,” “Before Removing,” or “After Updating.”

They allow developers to separate complex implementations that fit into a service or controller class, providing flexibility in separating components for easier management and handling.

TypeORM’s event listeners and subscribers feature utilizes the Observer Design Pattern for listening and subscribing to database events and queries, and Aspect-Oriented Programming (AOP) to specify event lifecycles.

Before we continue, let’s understand what is Observer Design Pattern.

The Observer design pattern is a behavioral pattern that allows an object (known as the subject) to maintain a list of dependents (observers) that are notified automatically of any state changes, usually by calling one of their methods. This pattern is used when there is a one-to-many relationship between objects, and changes in one object need to be reflected in other objects without them being tightly coupled.

A basic observer pattern example

To understand it better, let’s have a real life example:

Nowadays, we all have a YouTube account and we have YouTube channels we enjoy watching their videos. What we do is we subscribe to their channels and we get notified when they upload a new video.

That’s how observer pattern actually works, there is a specific event occuring (for our example is a YouTube channel uploading video to YouTube) and subscribers to that event are getting notified for this (for our example are YouTube users that are subscribed to that YouTube channel).

TypeORM has implemented Observer Pattern & Aspect Oriented Programming (AOP) for various lifecycle database events that happen when we interact with entities. This provides a great flexibility to make our code more readable and seperating concerns within application.

I have already an article about Aspect-Oriented Programming, you can read it from here:

Enough talk, time to see an real example in code!

Let’s say we have an user entity that we have implemented with TypeORM:

import { Column, Entity, PrimaryGeneratedColumn } from "typeorm";

@Entity()
export class User {

@PrimaryGeneratedColumn()
id: number

@Column({name: "NAME", nullable: false})
name: string

@Column({name: "SURNAME", nullable: false})
surname: string

@Column({name: "EMAIL", nullable: false})
email: string

@Column({name: "PASSWORD", nullable: false})
password: string

@Column({name: "IS_ACTIVE", nullable: false, default: true})
isActive: Boolean

@Column({name: "CREATED_AT", nullable: false})
created_at: Date

@Column({name: "UPDATED_AT", nullable: false})
updated_at: Date
}

And what I want to do is Before Inserting a user entity I want to hash it’s password and set created_at and updated_at to actual date and time it has been inserted.

Here’s how we can do this with Entity Listeners:


@BeforeInsert
beforeInsertingAUser() {
this.created_at = new Date()
this.updated_at = new Date()
const hashedPassword = bcrypt.hashSync(this.password, 10);
this.password = hashedPassword;
}

In this example, we have used Entity Listeners for User entity to listen before insert events to update created_at and updated_at and hash password with bcrypt library and set it to password.

Here’s how our User entity looks like now:


@Entity()
export class User {

@PrimaryGeneratedColumn()
id: number

@Column({name: "NAME", nullable: false})
name: string

@Column({name: "SURNAME", nullable: false})
surname: string

@Column({name: "EMAIL", nullable: false})
email: string

@Column({name: "PASSWORD", nullable: false})
password: string

@Column({name: "IS_ACTIVE", nullable: false, default: true})
isActive: Boolean

@Column({name: "CREATED_AT", nullable: false})
created_at: Date

@Column({name: "UPDATED_AT", nullable: false})
updated_at: Date

@BeforeInsert
beforeInsertingAUser() {
this.created_at = new Date()
this.updated_at = new Date()
const hashedPassword = bcrypt.hashSync(this.password, 10);
this.password = hashedPassword;
}
}

Here’s how we can do it with Entity Subscribers:

@EventSubscriber()
export class UserSubscriber implements EntitySubscriberInterface<User> {

listenTo() {
return User;
}

beforeInsert(event: InsertEvent<User>) {
event.entity.created_at = new Date()
event.entity.updated_at = new Date()
const hashedPassword = bcrypt.hashSync(event.entity.password, 10);
event.entity.password = hashedPassword;
}

So the idea is the same here for subscribers, they implement EntitySubscriberInterface<E> and they listen to the entity what we provide in listenTo() function and we create lifecycle events as functions with an event variable with lifecycle event type containing entity as generic type.

Entity Subscribers can listen to every entity events and you can create one entity subscriber to listen to every entity event that happens in application.

So if you have common events for every entity, you may consider using one subscriber for every entity, but if you have events that vary based on entity, you might consider seperating them to avoid complexities.

Also when you use entity subscribers, do not forget to configure in data source.

Here’s an example of configuring subscribers in data source:


import { DataSource } from "typeorm";
import dotenv from 'dotenv';

dotenv.config();

export const AppDataSource = new DataSource({
"type": "postgres",
"host": process.env.HOST,
"port": 5432,
"username": process.env.DB_USERNAME,
"password": process.env.DB_PASSWORD,
"database": process.env.DB_DATABASE,
"synchronize": true,
"logging": true,
"entities": ['src/entities/**/*.entity.ts'],
"migrations": ['src/migrations/**/*.ts'],
"subscribers": ['src/subscribers/**/*.subscriber.ts']

});

so DataSource contains a parameter subscribers and you have to pass the path of subscribers you have it in application.

What other events do Event Listeners & Subscribers have?

Besides of Before Insert, there are different events that you will use it for sure.

Here are some popular database events for listeners & subscribers:

Before Insert / After Insert

Before Update / After Update

Before Remove / After Remove

After Load

Before Transaction / After Transaction

For all database events, you may check documentation:
https://orkhan.gitbook.io/typeorm/docs/listeners-and-subscribers

Entity subscribers in TypeORM offer reusable event handling across multiple entities, while listeners are entity-specific. The choice between them depends on the scope and complexity of event handling needs, with subscribers being more generic and listeners focusing on entity-specific logic.

Thank you for reading. :)

Stackademic 🎓

Thank you for reading until the end. Before you go:

Published in Stackademic

Stackademic is a learning hub for programmers, devs, coders, and engineers. Our goal is to democratize free coding education for the world.

No responses yet

Write a response