I have an entity Message
that has a sender
(User
) and a receiver
, but the receiver
can either be a User
or a Channel
(both entities have the messagesIn
member), and I want to implement this using TypeORM relationships.
Would It be possible to do something like this with TypeORM (Postgres) / NestJS (or is there any conventional other way to achieve the same goal) ?
// message.entity.ts
@Entity()
export class Message {
@PrimaryGeneratedColumn('uuid')
id: string;
@Column()
content: string;
@ManyToOne(() => User, (snd) => snd.messagesOut)
sender: User;
// That line doesn't compile
@ManyToOne(() => User | Channel, (rcv) => rcv.messagesIn)
receiver: User | Channel;
};
What you're trying to do isn't possible because TypeORM needs to know exactly what entity you are trying to populate so then it can query in the right place.
TypeORM has some mechanisms that allows you to create a single table that all the records, both from User and Channel, will be inserted in, see more here.
But, if you want to have two dedicates table for each entity (aka Table Per Type Inheritance), I would suggest create another entity called Receiver
that contains common properties, that are related to the messaging mechanism, between User
and Channel
, plus a property called type
, that will define if the Receiver is a User
or a Channel
; and then reference the Receiver
entity in each one of the two tables.
export enum ReceiverType {
USER = 'USER',
CHANNEL = 'CHANNEL',
}
@Entity()
export class Receiver {
@PrimaryGeneratedColumn('uuid')
id: string;
// all common properties that you want related to messaging goes here
@OneToMany(() => Message, (msg) => msg.receiver)
messagesIn: Message[];
@Column({
type: "enum",
enum: ReceiverType,
})
type: ReceiverType
}
@Entity()
export class User {
// user properties...
@OneToOne(() => Receiver)
@JoinColumn()
receiver: Receiver
}
@Entity()
export class Channel {
// channel properties...
@OneToOne(() => Receiver)
@JoinColumn()
receiver: Receiver
}
Then, in the Message
entity, you will be able to reference Receiver
:
@Entity()
export class Message {
@PrimaryGeneratedColumn('uuid')
id: string;
@Column()
content: string;
@ManyToOne(() => User, (snd) => snd.messagesOut)
sender: User;
@ManyToOne(() => Receiver, (rcv) => rcv.messagesIn)
receiver: Receiver;
};
So, when you get a Message entity through the repository, you will receive something like:
{
id: string;
content: string;
sender: { ... };
receiver: {
// common properties you defined...
messagesIn: [ ... ],
type: ReceiverType,
}
}
With this information in hands, you will be able to access the data that you want and is necessary for the message sending flow, and, if you need some specific information that is only present in the User
or Channel
entity, you can get the type
from Receiver
and retrieve the respective entity by the receiver id, something like:
if (message.receiver.type === ReceiverType.USER) {
const user = await userRepository.findOne({
where: {
receiver: { id: message.receiver.id },
},
});
...
}
if (message.receiver.type === ReceiverType.CHANNEL) {
const user = await channelRepository.findOne({
where: {
receiver: { id: message.receiver.id },
},
});
...
}