Cuando trabajas con relaciones muchos a muchos en TypeORM, el decorator @ManyToMany resuelve la mayoría de los casos de forma automática: crea la tabla ternaria, gestiona los identificadores y te ofrece métodos de arrays para manipular la relación. Sin embargo, hay escenarios donde necesitas campos adicionales en esa tabla intermedia, y ahí es donde TypeORM te pide que tomes el control. Entender cuándo y cómo hacerlo marca la diferencia entre un modelo de datos limitado y uno verdaderamente funcional.
¿Por qué la relación many to many automática se queda corta?
Imagina una orden de compra. Una orden tiene muchos productos y un producto puede pertenecer a muchas órdenes: relación muchos a muchos clásica. Si usas el decorator @ManyToMany, TypeORM genera una tabla ternaria con solo dos columnas: product_id y order_id [01:05]. Eso funciona para saber qué productos están en la orden, pero no cuántos de cada producto pidieron.
Cuando necesitas un atributo extra como quantity (la cantidad de cada producto en la orden), TypeORM simplemente te dice: crea tú mismo la entidad para esa tabla ternaria [02:20]. La tabla ternaria es esa tabla intermedia que conecta dos entidades en una relación muchos a muchos, y al personalizarla ganas control total sobre los datos que almacena.
¿Cómo crear la entidad Order y su tabla ternaria paso a paso?
El primer paso es convertir la clase Order en una entidad real con el decorator @Entity. Sin este decorador, TypeORM no reconoce la clase y la migración no funciona correctamente [03:25].
La entidad Order incluye:
- Un ID autogenerado con
@PrimaryGeneratedColumn.
- Campos de fecha de creación y actualización como buena práctica.
- Una relación
@ManyToOne hacia Customer, ya que un cliente puede tener muchas órdenes, pero cada orden pertenece a un solo cliente [03:50].
En la entidad Customer se agrega la relación inversa con @OneToMany apuntando a un array de órdenes.
¿Cómo se estructura la entidad OrderItem?
Aquí está el corazón de la solución. Se crea un nuevo archivo para la entidad OrderItem que representa la tabla ternaria personalizada [05:10]. Esta clase necesita:
- El decorator
@Entity y una llave primaria autogenerada.
- Un campo
quantity con el decorator @Column de tipo entero, que es precisamente el atributo adicional que justifica manejar la tabla ternaria manualmente [06:15].
- Una relación
@ManyToOne hacia Product: cada ítem referencia un producto específico.
- Una relación
@ManyToOne hacia Order: cada ítem pertenece a una orden.
La relación muchos a muchos con tabla ternaria personalizada se descompone en dos relaciones uno a muchos implícitas. OrderItem es el punto de unión entre Order y Product, pero ahora con datos propios.
¿Cuándo conviene una relación bidireccional?
Desde Order hacia OrderItem la relación bidireccional es muy útil: al consultar una orden, quieres saber sus ítems de compra [08:30]. Se agrega @OneToMany en la entidad Order apuntando a OrderItem.
En cambio, desde Product hacia OrderItem no se habilita la relación bidireccional, porque no resulta funcional saber desde un producto a cuáles ítems de orden está asociado [07:45]. Esta decisión depende de la regla de negocio: si el query no tiene sentido práctico, no vale la pena agregar la relación inversa.
¿Cómo registrar las entidades y correr la migración?
Una vez creadas las entidades, se agregan al arreglo de entidades del módulo correspondiente (UserModule en este caso) [10:40]:
Después se genera y ejecuta la migración desde la terminal:
bash
npm run migrations:generate -- orders
npm run migrations:run
Al verificar en PGAdmin, la tabla ternaria ahora muestra un id, created_at, updated_at, product_id, order_id y quantity [12:05]. Esto contrasta con la tabla ternaria automática de TypeORM que solo tendría los dos identificadores.
Lo que se pierde al manejar la tabla ternaria manualmente es el acceso a los métodos de arrays que TypeORM ofrece para agregar o remover relaciones automáticamente [12:45]. Lo que se gana es una manipulación más precisa de los datos intermedios, con campos personalizados que reflejan mejor la lógica del negocio.
¿Has tenido que personalizar tablas ternarias en tus proyectos? Comparte tu experiencia y cómo resolviste la gestión de esos campos adicionales.