Juan Caballero
Jesús Alberto Martínez Hernández
npm run migrations:generate -- orders
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:
@PrimaryGeneratedColumn.@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:
@Entity y una llave primaria autogenerada.quantity con el decorator @Column de tipo entero, que es precisamente el atributo adicional que justifica manejar la tabla ternaria manualmente [06:15].@ManyToOne hacia Product: cada ítem referencia un producto específico.@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]:
OrderEntity.OrderItem.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.
Juan Caballero
Jesús Alberto Martínez Hernández
npm run migrations:generate -- orders
Ricardo Tribaldos Hernández
No sería mejor tener todas las entidades en un solo lugar (ej. project_root/entities) en lugar de tenerlas dentro de cada módulo?
Carlos Alberto Cabrera Quiroga
creo lo divide en modulos para mejorar la organización (arquitectura), que ayuda a que sea mas escalable, mantenible(en equipo) y testeable.
Jeremi Airon Enock Ocando
Este curso en muy diferente al de Persistencia de datos con MongoDB. Antes de ver este curso, termine el de MongoDB, y sentí que había muchas cosas que no podía hacer de forma sencilla, comparado con TypeORM, Mongoose se queda muy corto.
Excelente curso.
Andres Trujillo
Gracias por tu aporte, en pasadas ocasiones habia trabajado simepre con Mongosee y aunque facilita ciertas cosas a nivel de relaciones, trae complejidad en el manejo de los datos dentro de una aplicación, ahora con nestJs puedo trabajar sin complejidades con bases de datos relacionales, he decidido sacar mongoDB de mi stack por ahora para seguir aprendiendo a profundidad postgres, saludos.
Pablo Arango Bustamante
No entiendo muy bien cómo se divide la por módulos. Porque en el modulo de usuarios, se tiene operaciones de ordenes? ordenes no debería tener un modulo aparte ?
Juan David Merchán Torres
Hola 👋
Por que el encargado de las ordenes es el usuario.
Mira como en las entities de products solo están brand category product, todo lo relacionado al producto, y en las entities de users están user customer y order con su respectiva tabla intermedia.
Espero haberte ayudado :rocket:
Resumen de la clase
// src\users\entities\order.entity.ts import { PrimaryGeneratedColumn, UpdateDateColumn, CreateDateColumn, ManyToOne, Entity, OneToMany, } from 'typeorm'; import { Customer } from './customer.entity'; import { OrderItem } from './order-item.entity'; @Entity() export class Order { @PrimaryGeneratedColumn() id: number; @CreateDateColumn({ type: 'timestamptz', default: () => 'CURRENT_TIMESTAMP', }) createAt: Date; @UpdateDateColumn({ type: 'timestamptz', default: () => 'CURRENT_TIMESTAMP', }) updateAt: Date; @ManyToOne(() => Customer, (customer) => customer.orders) customer: Customer; @OneToMany(() => OrderItem, (item) => item.order) items: OrderItem[]; }
// src\users\entities\customer.entity.ts import { ... OneToMany, } from 'typeorm'; import { User } from './user.entity'; import { Order } from './order.entity'; @Entity() export class Customer { ... @OneToMany(() => Order, (order) => order.customer) orders: Order[]; }
import { PrimaryGeneratedColumn, UpdateDateColumn, CreateDateColumn, Entity, Column, ManyToOne, } from 'typeorm'; import { Product } from '../../products/entities/product.entity'; import { Order } from './order.entity'; @Entity() export class OrderItem { @PrimaryGeneratedColumn() id: number; @CreateDateColumn({ type: 'timestamptz', default: () => 'CURRENT_TIMESTAMP', }) createAt: Date; @UpdateDateColumn({ type: 'timestamptz', default: () => 'CURRENT_TIMESTAMP', }) updateAt: Date; @Column({ type: 'int' }) // columna que se agrega a la relacion quantity: number; @ManyToOne(() => Product) // en este caso no es funcional la relación bidireccional product: Product; @ManyToOne(() => Order, (order) => order.items) order: Order; }
... import { Order } from './entities/order.entity'; import { OrderItem } from './entities/order-item.entity'; @Module({ imports: [ ... TypeOrmModule.forFeature([User, Customer, Order, OrderItem]), ], ... }) export class UsersModule {}
npm run migrations:generate --orders npm run migrations:run