Cómo definir relaciones de N a M (de muchos a muchos) entre entidades de TypeORM en el contexto de una aplicación desarrollada con NestJS. Explicaciones para entender y saber manejar los decoradores @ManyToMany y @JoinTable.
En el pasado artículo introdujimos los mecanismos de creación de relaciones entre entidades en Nest. Vimos con detalle un ejemplo de implementación de una relación de "1 a muchos", así que en este artículo vamos a avanzar un poco más, para implementar relaciones de "muchos a muchos". Lo haremos con TypeORM en una aplicación Nest, mediante la aplicación de decoradores en las entidades y la asignación de las relaciones programáticamente, mediante código.
Damos por hecho que sabes qué es una relación de "muchos a muchos" o de "N a M" como a veces se la llama también. Si no es así, puedes consultar más sobre bases de datos.
Nuestras entidades en el modelo de base de datos
Para este ejemplo vamos a trabajar con una entidad para almacenar las posibles tallas de un producto. Los tamaños de los productos pueden ser cosas como "M", "S", "L". Nuestras entidades serían:
- Productos (Product)
- Talla (Size)
Es una relación de muchos a muchos porque un producto puede tener varias tallas y una talla puede estar presente en múltiples productos.
Creando la entidad de Size
Nos basaremos en la aplicación desarrollada a lo largo del Manual de Nest. En artículos anteriores ya habíamos creado la entidad Product, por lo que solamente necesitamos crear una nueva entidad, Size.
El código completo de nuestra entidad es este:
import { Column, Entity, ManyToMany, PrimaryGeneratedColumn } from 'typeorm';
import { Product } from './product.entity';
@Entity('sizes')
export class Size {
@PrimaryGeneratedColumn()
id: number;
@Column('varchar', { length: 5 })
size: string;
}
Simplemente hemos creado una entidad con su id autonumérico y un nombre para la talla en la columna "size".
Decorador @ManyToMany
En el código de la entidad Size nos falta por definir la relación que tendrá con la entidad de productos. Para ello usamos el decorador @ManyToMany
de TypeORM.
Este decorador es parecido a otros de relaciones que hemos visto anteriormente, así que no te sorprenderá mucho.
Creando relación muchos a muchos en Size
Dentro de la entidad Size, definimos la relación con la entidad Product de esta manera:
@ManyToMany(() => Product, (product) => product.sizes)
products: Product[];
El decorador @ManyToMany()
lo alimentamos con dos parámetros que son de tipo función.
- La primera función no recibe nada y devuelve el tipo de la entidad con la que estamos relacionando. En este caso estamos definiendo la relación en Size, por lo que esta primera función devuelve la entidad relacionada:
Product
- La segunda función define la inversa de la relación, es decir, teniendo un objeto de la entidad relacionada, ¿cómo llego a la entidad que estoy implementando? Por ello recibimos un objeto "
product
" y devolvemos la propiedad "sizes
" es ese objeto. La propiedad "sizes
" aún no existe en la entidad Product, pero ahora la crearemos al definir la relación en esa tabla.
Finalmente indicamos que la entidad Size
tendrá una propiedad llamada "products
" (en plural) que es un array de elementos de clase Product
.
Creando relación muchos a muchos en Product
Dentro de la entidad Product
, definimos la relación con la entidad Size
de esta manera:
@JoinTable()
@ManyToMany(() => Size, (size) => size.products)
sizes: Size[];
El decorador @ManyToMany()
funciona de manera análoga a lo que acabamos de explicar.
- Indicamos que desde
product
nos relacionamos conSize
- Indicamos que desde el objeto
size
, podremos llegar a los productos mediantesize.products
Decorador @JoinTable
La novedad en este código es la definición del decorador @JoinTable
al crear la relación. Este decorador sirve para indicar la tabla principal en una relación de "muchos a muchos", o quizás más claro, indica a quién pertenece aquello que se está relacionando.
Por ejemplo, ¿Los productos tienen tallas? ¿o quizás las tallas tengan productos? En este caso está claro que los productos son los que tienen tallas, por lo tanto, el decorador @JoinTable
se coloca en los productos.
Si no está tan claro cuál es la entidad principal, entonces puedes colocar @JoinTable en la entidad que prefieras.
Agregar la entidad Size al products.module.ts
Para que el framework tome esta entidad en consideración necesitamos agregarla al módulo desde donde vamos a usarla. En este caso teníamos un módulo de productos donde ya veníamos usando otras entidades, así que debemos añadir simplemente Size
al array de entidades definido mediante el método forFeature()
de TypeOrmModule.
Este es el código que nos quedará en el decorador @Module
de products.module.ts
.
@Module({
imports: [TypeOrmModule.forFeature([Product, Review, Size])],
controllers: [ProductsController, ReviewsController],
providers: [ProductsService, ReviewsService]
})
Ahora, si arrancamos la aplicación veremos que se generan un par de tablas nuevas. La primera para almacenar los distintos nombres de tallas, en la tabla "sizes
".
Luego tenemos la tabla que relaciona productos con tallas, que contiene los identificadores de la tabla de producto
y la tabla de size
.
Como es una relación de "muchos a muchos" sabes que genera una nueva tabla, llamada habitualmente tabla pivote, con los identificadores de los registros que se tienen que relacionar.
La tabla en nuestro caso se llama "products_sizes_sizes
" y tendrá la siguiente estructura.
TABLA products_sizes_sizes:
+------------+---------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+------------+---------+------+-----+---------+-------+
| productsId | int(11) | NO | PRI | NULL | |
| sizesId | int(11) | NO | PRI | NULL | |
+------------+---------+------+-----+---------+-------+
El nombre de la tabla pivote puede variar dependiendo del caso. Siempre pone el nombre de la tabla principal (definida por que es la que tiene el @JoinTable
, seguido del nombre de la relación en plural, y luego el nombre de la tabla relacionada. En este caso recuerda que nuestra entidad "Size" dijimos que el nombre de la tabla era "sizes
" por el decorador @Entity("sizes")
.
Recuerda que las tablas se van a crear automáticamente solamente si tienes la propiedad synchronize
con el valor true en la conexión definida en el módulo principal en el TypeOrmModule.forRoot()
.
Conclusión
Acabamos de aprender a definir las relaciones de muchos a muchos en nuestras entidades. Como has podido comprobar es un simple trabajo de configuración en TypeScript. Entran en nuevo varios conceptos y hay que usar los decoradores adecuados, pero el trabajo es muy simple.
Llegado a este punto tenemos la relación definida y debemos haber visto la tabla ya creada en nuestra base de datos. A partir de ahora solamente se trata de agregar datos relacionados, para lo que necesitaremos entrar con programación. Esta parte la veremos en la siguiente entrega del manual.
Miguel Angel Alvarez
Fundador de DesarrolloWeb.com y la plataforma de formación online EscuelaIT. Com...