NestJS

Nest

Tutorial Nest: Filtrados en consultas de repositorios

Aprende a filtrar datos en repositorios NestJS usando find() y QueryBuilder para consultas eficientes y seguras con TypeORM.

Aprende Nest y certifícate

En el desarrollo de aplicaciones, especialmente aquellas que interactúan con bases de datos, es común la necesidad de filtrar o refinar los resultados obtenidos a partir de ciertos criterios. NestJS, proporciona herramientas para facilitar esta tarea cuando se trabaja con bases de datos.

En este contexto, el filtrado de datos se refiere a la práctica de definir y aplicar criterios específicos para limitar los datos recuperados de una base de datos. Esto se logra principalmente a través del uso de repositorios.

Repositorios en NestJS

Un repositorio es un patrón de diseño que separa la lógica que recupera los datos y la lógica que accede a ellos.

En NestJS, cuando se utiliza el paquete @nestjs/typeorm, se puede acceder a estos repositorios y utilizarlos para interactuar con la base de datos.

Filtrando Datos con el Método find()

El método find() de un repositorio es tu primera herramienta para obtener datos. Acepta un objeto de opciones (FindManyOptions) que permite especificar criterios de filtrado (where), ordenación (order), paginación (skip, take), y más.

Vamos a usar una entidad Usuario para nuestros ejemplos:

// src/usuario/usuario.entity.ts
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';

@Entity('usuarios')
export class Usuario {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  nombre: string;

  @Column()
  edad: number;

  @Column({ default: true })
  isActive: boolean;
}

Ahora, en tu servicio, puedes inyectar el repositorio y usar find():

// src/usuario/usuario.service.ts
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository, MoreThan, LessThan, Like, In, And, Or } from 'typeorm'; // Importa los operadores de TypeORM
import { Usuario } from './usuario.entity';

@Injectable()
export class UsuarioService {
  constructor(
    @InjectRepository(Usuario)
    private readonly usuarioRepository: Repository<Usuario>,
  ) {}

  // Obtener todos los usuarios
  async findAll(): Promise<Usuario[]> {
    return this.usuarioRepository.find();
  }

  // Filtrar: Obtener usuarios mayores de 18 años
  async obtenerAdultos(): Promise<Usuario[]> {
    return this.usuarioRepository.find({
      where: {
        edad: MoreThan(18), // Operador: mayor que
      },
    });
  }

  // Filtrar: Combinando múltiples criterios (AND implícito)
  // Obtener usuarios mayores de 18 años Y cuyo nombre comience con 'A'
  async obtenerAdultosConA(): Promise<Usuario[]> {
    return this.usuarioRepository.find({
      where: {
        edad: MoreThan(18),
        nombre: Like('A%'), // Operador: coincide con el patrón (ej. 'A%', '%xyz%', '%abc')
      },
    });
  }

  // Filtrar: Usando operadores lógicos (OR explícito)
  // Obtener usuarios que tienen 18 años O cuyo nombre empiece con 'B'
  async obtenerPorEdadOInicialB(): Promise<Usuario[]> {
    return this.usuarioRepository.find({
      where: [ // Usar un array para condiciones OR
        { edad: 18 },
        { nombre: Like('B%') }
      ]
      // Alternativa con operador Or explícito (menos común para ORs simples):
      // where: {
      //   [Or]: [
      //     { edad: 18 },
      //     { nombre: Like('B%') }
      //   ]
      // }
    });
  }

  // Filtrar: Usando el operador IN
  // Obtener usuarios con IDs específicos
  async obtenerPorIds(ids: number[]): Promise<Usuario[]> {
    return this.usuarioRepository.find({
      where: {
        id: In(ids), // Operador: El ID está dentro del array de IDs
      },
    });
  }
}

Otros operadores comunes de comparación disponibles para where:

  • Equal(value): Compara si es igual a value.
  • Not(value): Compara si es diferente de value.
  • Between(from, to): Compara si está entre from y to (inclusive).
  • IsNull(): Compara si el valor es NULL.
  • And(condition1, condition2): Combina condiciones con AND.
  • Or(condition1, condition2): Combina condiciones con OR.

Ordenando y Paginando Resultados

Para controlar el orden y el número de resultados, se usan las propiedades order, take y skip en el método find():

  • order: Define el orden de los resultados (ascendente ASC o descendente DESC).
  • take: Limita el número de resultados devueltos (el equivalente a LIMIT).
  • skip: Salta un número determinado de resultados (el equivalente a OFFSET), útil para la paginación.
// Dentro de UsuarioService
async obtenerTop10AdultosOrdenados(): Promise<Usuario[]> {
  return this.usuarioRepository.find({
    where: {
      edad: MoreThan(18),
    },
    order: {
      nombre: 'ASC', // Ordenar por nombre de forma ascendente
    },
    take: 10, // Obtener solo los primeros 10 resultados
  });
}

// Ejemplo de Paginación
async obtenerUsuariosPaginados(page: number = 1, limit: number = 10): Promise<Usuario[]> {
  const skip = (page - 1) * limit; // Calcular cuántos registros saltar
  return this.usuarioRepository.find({
    order: { nombre: 'ASC' },
    take: limit, // Número máximo de resultados por página
    skip: skip, // Número de resultados a saltar (offset)
  });
}

Filtrado Personalizado Utilizando QueryBuilder

Cuando las necesidades de filtrado se vuelven más complejas (ej., múltiples JOINs, subconsultas, agrupaciones complejas), el **QueryBuilder** de TypeORM ofrece la máxima flexibilidad. Te permite construir consultas SQL de forma programática y con seguridad de tipos.

Para usar QueryBuilder, lo invocas desde tu repositorio inyectado.

// Dentro de UsuarioService
// Obtener usuarios mayores de 18 años, excluyendo aquellos cuyo nombre comience con 'A'
async obtenerAdultosExcluyendoA(): Promise<Usuario[]> {
  return this.usuarioRepository // Usa el repositorio inyectado
    .createQueryBuilder('usuario') // Crea un QueryBuilder, 'usuario' es el alias para la entidad Usuario
    .where('usuario.edad > :edad', { edad: 18 }) // Define una condición WHERE con parámetros seguros
    .andWhere('usuario.nombre NOT LIKE :nombre', { nombre: 'A%' }) // Añade otra condición AND
    .getMany(); // Ejecuta la consulta y obtiene múltiples resultados
}

// Combinar entidades relacionadas (Ejemplo: Obtener usuarios que tienen un libro específico)
// Suponemos que la entidad Usuario tiene una relación 'OneToMany' con una entidad 'Libro'
// y que la entidad Libro tiene una columna 'titulo'.
async obtenerUsuariosConLibro(tituloLibro: string): Promise<Usuario[]> {
  return this.usuarioRepository
    .createQueryBuilder('usuario')
    // Realiza un JOIN con la relación 'libros' del usuario, y asigna el alias 'libro'
    .innerJoinAndSelect('usuario.libros', 'libro', 'libro.titulo = :titulo', {
      titulo: tituloLibro,
    })
    .getMany(); // Obtiene los usuarios que cumplen el criterio
}

Con createQueryBuilder, tienes un control mucho más detallado sobre la consulta SQL generada, lo que es importantísimo para escenarios avanzados.

Buenas prácticas

  1. Reutilización de Código: Si encuentras que repites lógicas de filtrado complejas, considera encapsularlas en métodos de servicio específicos o incluso en métodos personalizados del repositorio.
  2. Evitar Filtrados en Memoria: Siempre que sea posible, realiza los filtros directamente en la base de datos (usando where, order, QueryBuilder). Recuperar todos los datos y filtrarlos después en la aplicación es ineficiente y consume más recursos.
  3. Documentación: Las consultas pueden volverse complejas. Es esencial documentar qué hace cada método de filtrado y cuál es su propósito para facilitar la comprensión y el mantenimiento por parte de otros desarrolladores (y tu "yo futuro").
  4. Parámetros Seguros: Siempre utiliza parámetros (:nombre, :edad) en tus consultas de QueryBuilder para evitar ataques de inyección SQL. TypeORM se encarga de la seguridad.

Conclusión

NestJS y TypeORM ofrecen herramientas poderosas y flexibles para el filtrado de datos en tus aplicaciones. Desde las consultas sencillas utilizando el método find() de los repositorios con operadores declarativos, hasta las construcciones más avanzadas con QueryBuilder para un control granular, puedes manejar una amplia gama de escenarios y necesidades de acceso a datos. Dominar estas técnicas te permitirá construir aplicaciones eficientes, rápidas y seguras.

Aprende Nest online

Otras lecciones de Nest

Accede a todas las lecciones de Nest y aprende con ejemplos prácticos de código y ejercicios de programación con IDE web sin instalar nada.

Accede GRATIS a Nest y certifícate

Ejercicios de programación de Nest

Evalúa tus conocimientos de esta lección Filtrados en consultas de repositorios con nuestros retos de programación de tipo Test, Puzzle, Código y Proyecto con VSCode, guiados por IA.