Gestionar relaciones muchos a muchos puede volverse incómodo cuando necesitas agregar o eliminar una sola categoría de un producto y el enfoque tradicional te obliga a enviar todo el array de IDs cada vez. TypeORM, combinado con métodos nativos de arrays en JavaScript como filter y push, ofrece una forma más limpia y profesional de resolver este problema.
¿Cómo funciona la actualización del array completo en una relación muchos a muchos?
Cuando creas un producto con varias categorías, simplemente asignas el array de IDs y TypeORM resuelve la relación. Para actualizarlo, podrías recibir un nuevo array de categorías en el método update del servicio, buscar esas categorías y reasignarlas al producto [01:15].
El problema aparece cuando la lista crece. Si un producto pertenece a diez categorías y solo quieres eliminar una, deberías enviar las nueve restantes. Si quieres agregar una, deberías enviar las diez anteriores más la nueva. Enviar siempre el array completo se vuelve propenso a errores.
¿Por qué separar la manipulación del array en métodos independientes?
Una buena práctica es crear métodos específicos para agregar o remover elementos de la relación, en lugar de depender únicamente del método update. Esto permite endpoints dedicados que reciben solo el ID del producto y el ID de la categoría a modificar [03:22].
¿Cómo eliminar una categoría de un producto con filter?
El método removeCategoryByProduct recibe el productId y el categoryId. Primero, se obtiene el producto incluyendo la relación hacia categories, algo fundamental para que el array exista en memoria [05:50].
typescript
async removeCategoryByProduct(productId: number, categoryId: number) {
const product = await this.productsRepo.findOne(productId, {
relations: ['categories'],
});
product.categories = product.categories.filter(
(item) => item.id !== categoryId,
);
return this.productsRepo.save(product);
}
- Se usa
filter para generar un nuevo array sin la categoría indicada.
filter es un método inmutable: no modifica el array original, sino que devuelve uno nuevo.
- Después del filtrado, se guarda el producto con
save.
¿Por qué es importante cargar la relación antes de filtrar?
Sin la opción relations: ['categories'], el array categories queda como undefined y el método filter lanza un error: "cannot read property filter of undefined" [05:38]. TypeORM necesita la instrucción explícita para traer los datos relacionados.
¿Qué papel juega ParseIntPipe en los parámetros de URL?
Los parámetros que llegan desde la URL siempre son strings, aunque declares el tipo como number en TypeScript. Esa declaración de tipo es solo una referencia en tiempo de compilación, no una transformación real [07:12].
El pipe ParseIntPipe resuelve esto: convierte el string a número entero y, si el valor no es numérico, rechaza la petición antes de llegar al servicio.
typescript
@Delete(':id/category/:categoryId')
deleteCategory(
@Param('id', ParseIntPipe) id: number,
@Param('categoryId', ParseIntPipe) categoryId: number,
) {
return this.productsService.removeCategoryByProduct(id, categoryId);
}
¿Cómo agregar una categoría a un producto existente con push?
El método addCategoryToProduct también recibe productId y categoryId. Obtiene el producto con su relación y, además, busca la categoría para verificar que exista [09:08].
typescript
async addCategoryToProduct(productId: number, categoryId: number) {
const product = await this.productsRepo.findOne(productId, {
relations: ['categories'],
});
const category = await this.categoriesRepo.findOne(categoryId);
product.categories.push(category);
return this.productsRepo.save(product);
}
push es un método mutable: modifica directamente el array original, por lo que no necesitas reasignar.
- Después del push, se guarda el producto con la nueva relación.
En el controlador, este método se expone como un endpoint PUT que recibe el ID del producto y el ID de la categoría como parámetros de ruta [10:30]. No se necesita body, toda la información viaja en la URL.
La diferencia entre métodos mutables e inmutables de JavaScript es clave aquí: push cambia el array en el que se ejecuta, mientras que filter retorna uno nuevo sin alterar el original. Conocer esta distinción te permite elegir el enfoque correcto según el caso.
Con estos dos endpoints independientes, administrar relaciones muchos a muchos se simplifica considerablemente. Si quieres compartir algún patrón diferente para manejar estas relaciones, deja tu aporte en los comentarios.