Consultas Avanzadas con Rangos de Precios en MongoDB

Clase 16 de 24Curso de NestJS: Persistencia de Datos con MongoDB

Contenido del curso

Mongoose

Resumen

Construir consultas avanzadas en MongoDB es fundamental cuando necesitas ir más allá de un simple find. Aquí se explora cómo implementar filtros por rango de precios en una API con NestJS y Mongoose, aprovechando validaciones condicionales y queries dinámicos que escalan con facilidad.

¿Cómo agregar parámetros de precio mínimo y máximo al DTO?

El punto de partida es el DTO de filters, donde ya existían limit y offset para la paginación. Ahora se incorporan dos nuevos campos: minPrice y maxPrice [0:18]. Ambos son de tipo numérico y representan el rango de precios que el usuario quiere consultar.

Para decorarlos correctamente se aplican estas reglas:

  • Se marca cada campo como opcional con @IsOptional(), de modo que si no se envían, la API devuelve todos los registros.
  • Se usa @IsPositive() en lugar de solo @Min(0), garantizando que los valores sean al menos uno [1:05].

¿Qué es validateIf y por qué usarlo?

Una situación interesante aparece cuando el usuario envía minPrice pero omite maxPrice. Sin el límite superior, el rango queda incompleto. Para resolver esto se utiliza el decorador ValidateIf [1:30], que convierte un campo en obligatorio de forma condicional.

En la práctica, maxPrice deja de ser opcional y se vuelve obligatorio si y solo si minPrice existe. La implementación luce así:

typescript @ValidateIf((params) => params.minPrice) @IsPositive() maxPrice: number;

Esto permite crear dependencias entre parámetros de un query sin romper la flexibilidad de la API.

¿Cómo construir filtros dinámicos en el servicio?

Dentro del servicio, además de extraer limit y offset, ahora se desestructuran minPrice y maxPrice desde los parámetros [2:08]. La estrategia consiste en crear un objeto filters que inicia vacío y se va llenando según las condiciones que se cumplan.

Este objeto se tipa con FilterQuery de Mongoose, asociado al modelo de productos [2:30]:

typescript const filters: FilterQuery<Product> = {};

  • Si filters está vacío, Mongo simplemente no aplica ningún filtro y devuelve todo.
  • Si se detectan minPrice y maxPrice, se agrega la condición de rango.
  • Este patrón es escalable: puedes agregar filtros por descripción, nombre, marca o cualquier otro campo sin reestructurar la lógica [2:50].

¿Cómo funciona el operador de rango gte y lte en MongoDB?

Para filtrar por rango no se puede comparar con igualdad directa. MongoDB ofrece operadores especiales dentro del documento de consulta [3:20]:

typescript if (minPrice && maxPrice) { filters.price = { $gte: minPrice, $lte: maxPrice }; }

  • $gte (greater than or equal): filtra documentos cuyo precio sea mayor o igual al valor mínimo.
  • $lte (less than or equal): filtra documentos cuyo precio sea menor o igual al valor máximo.

El objeto filters se pasa directamente al método find junto con la paginación, y Mongoose se encarga de traducirlo a la consulta correcta.

¿Cómo verificar que los filtros funcionan correctamente?

Al probar en Insomnia sin limit ni offset, se valida el comportamiento paso a paso [3:50]:

  • Enviar solo minPrice=50 sin maxPrice produce un error de validación indicando que maxPrice es obligatorio y debe ser positivo. La condicional de ValidateIf funciona.
  • Enviar minPrice=50 y maxPrice=80 retorna productos con precios de 50, 60, 70 y 80 [4:15].
  • Cambiar el rango a minPrice=10 y maxPrice=30 devuelve productos de 10, 20 y 30 [4:30].

Este patrón de filtros dinámicos es reutilizable en cualquier controlador. Puedes extenderlo para buscar por precio exacto, por nombre de producto o por categoría, simplemente agregando condiciones al objeto filters antes de ejecutar la consulta.

      Consultas Avanzadas con Rangos de Precios en MongoDB