Manipulación de Relaciones Muchos a Muchos en Controladores

Clase 23 de 36Curso de NestJS: Persistencia de Datos con TypeORM

Contenido del curso

Relaciones

Resumen

Trabajar con relaciones muchos a muchos va más allá de definir entidades: el verdadero reto está en manipular esas relaciones desde los controladores y servicios. Aquí se aborda paso a paso cómo recibir un array de IDs desde el payload, resolver las categorías asociadas y asignarlas correctamente a un producto usando TypeORM en NestJS.

¿Cómo preparar el DTO para recibir múltiples categorías?

El punto de partida es el Data Transfer Object (DTO). Cuando un producto puede pertenecer a varias categorías, necesitamos un campo que reciba un array de IDs [01:00].

Dentro del CreateProductDTO se agrega un campo categoriesIds con las siguientes validaciones:

  • Debe ser de solo lectura (read-only).
  • No puede estar vacío.
  • Debe ser un array, validado con el decorador @IsArray() de class-validator [01:30].

Este DTO viaja desde el payload al controlador, y del controlador al servicio, donde finalmente se resuelve la relación.

¿Por qué inyectar el repositorio directamente en lugar de usar el servicio?

Una decisión arquitectónica importante surge al resolver las categorías. El servicio de categorías tiene métodos como findAll, findOne, create y update, pero no tiene un método para buscar múltiples IDs a la vez [02:30].

TypeORM ofrece el método findByIds en el repositorio, que permite enviar un array de IDs y recibir todos los modelos correspondientes en una sola consulta [03:15]. En lugar de crear una abstracción innecesaria en el servicio de categorías, se inyecta directamente el repositorio:

typescript @InjectRepository(Category) private categoryRepo: Repository<Category>

Con esto, la resolución es directa:

typescript if (data.categoriesIds) { const categories = await this.categoryRepo.findByIds(data.categoriesIds); newProduct.categories = categories; }

Se busca el grupo de categorías, se asigna al producto y con save se persiste la relación [03:45].

¿Qué problema genera reutilizar el servicio de marcas?

Al crear un producto, se reutilizaba brandService.findOne para resolver la marca. Sin embargo, ese método incluía la relación products como parte del query [06:00]. El resultado era que al crear un producto, la respuesta devolvía la marca con todo su array de productos asociados, lo cual genera un payload innecesariamente pesado.

Esto representa un problema de rendimiento: se envía información que el usuario no necesita, consumiendo ancho de banda y recursos [07:30]. La solución fue inyectar también el repositorio de marcas directamente:

typescript @InjectRepository(Brand) private brandRepo: Repository<Brand>

Al usar brandRepo.findOne, se obtiene únicamente la entidad sin relaciones extras, a menos que se especifiquen explícitamente.

¿Cuándo conviene resolver relaciones en el detalle del producto?

No siempre es necesario traer todas las relaciones. En el listado general de productos (GET /products), probablemente no necesites el array completo de categorías. Pero en el detalle de un producto sí tiene sentido [09:00]:

typescript this.productRepo.findOne(id, { relations: ['brand', 'categories'], });

Esto devuelve el producto con la marca y el array de categorías asociadas [09:50].

¿Cómo resolver la relación inversa en categorías?

La relación muchos a muchos es bidireccional. Si al consultar el detalle de una categoría quieres saber qué productos le pertenecen, basta con agregar la relación en el findOne del controlador de categorías [10:30]:

typescript this.categoryRepo.findOne(id, { relations: ['products'], });

El resultado muestra la categoría con su array de productos asociados, confirmando que una categoría puede tener muchos productos y un producto puede tener muchas categorías [11:00].

Algunos puntos clave para recordar:

  • Usa findByIds para resolver múltiples entidades en una sola consulta.
  • Inyecta repositorios directamente cuando el servicio agrega abstracciones innecesarias.
  • Controla qué relaciones se resuelven según el contexto: listado vs. detalle.
  • Vigila el tamaño del response para evitar enviar datos que el consumidor no necesita.

Con las relaciones uno a uno, uno a muchos y muchos a muchos cubiertas, queda pendiente un tema fundamental: cómo modificar dinámicamente ese array de relaciones, por ejemplo, agregar o quitar categorías de un producto existente. ¿Cómo lo resolverías tú?