> Manuales > Manual de NestJS

Veremos cómo usar las variables que nos llegan por query string en NestJS para filtrar los datos que nos ofrecen los endpoints de nuestra API. Para ello usaremos DTO, validaciones y el método find() de TypeORM.

Búsquedas en entidades usando variables en Query String con NestJS

En el día de hoy en el Manual de Nest vamos a abordar un tema de uso muy frecuente en las aplicaciones web, para el que Nest nos ofrece una solución sencilla y ágil, como de costumbre. Se trata de una práctica en la que realizaremos búsquedas con TypeORM usando los datos que nos llegan desde el query string.

Usar el querystring es muy típico en las aplicaciones y APIs para refinar los resultados de las consultas que te ofrecen los endpoints. Por ejemplo, te damos algunos casos de uso frecuentes:

Para todos estos detalles sobre la consulta que deseamos componer lo más estándar en el mundo de las API REST es que usemos el endpoint GET sobre la raíz del recurso, enviando todos los datos de la consulta como variables en el query string.

Por ejemplo, si queremos que nos muestren únicamente 4 productos podríamos hacer una consulta como esta:

http://localhost:3000/products?limit=4

Si necesitamos que además ordene los datos por stock podríamos hacer una consulta como:

http://localhost:3000/products?limit=4&order=stock 

Las consultas de esta manera pueden complicarse todo lo que sea necesario, usando un mismo endpoint del API, manejando siempre un juego de datos enviados, que generalmente son todos opcionales.

Recibiendo datos de la consulta en el controlador

Mediante el decorador @Query podemos recibir todos los datos de la consulta. Vamos a comenzar con un ejemplo sencillo, que iremos luego complicando. En este ejemplo simplemente vamos a tener en cuenta la opción "limit" en el query string.

Para hacer un código robusto, tenemos que tener en cuenta un par de detalles:

El código propuesto en esta primera aproximación sería el siguiente:

@Get()
async getAllProducts(@Query() query): Promise<Product[]> {
  let limit = Number(query.limit) ?? 10;
  if(isNaN(limit)) {
    limit = 3;
  }
  return this.productsService.getAll(limit);
}

Como ves, una vez validado el valor limit, y definido el valor predeterminado, por si no lo mandan, podemos enviarlo al servicio para que se tenga en cuenta.

Contando con limit en el servicio

Luego, en el servicio tendríamos que componer correctamente la consulta con find(), para tener en cuenta esta limitación en el número de registros a mostrar.

getAll(limit: number): Promise<Product[]> {
  return this.productsRepository.find({
    take: limit,
  });
}

Recuerda que en un artículo anterior explicamos todas las opciones de configuración que podemos enviar al método find().

Manejando múltiples opciones en el Query String

Ahora pensemos en la posibilidad de que nos manden muchos datos para la consulta, como podrían ser:

Son tres ideas simples, pero podrían ser muchas más atendiendo a las necesidades de los clientes que van a consultar el API.

Lo que salta a la vista es que hacer todas las validaciones a mano sobre cada uno de los datos que podemos llegar a recibir puede ser demasiado laborioso. Así que vamos a hacer una estrategia de validación basada en pipes con ValidationPipe.

Para aprender las bases de ValidationPipe, por favor consulta el artículo de validación en Nest.

Creando un DTO

Vamos a comenzar por crear un DTO para la validación de los datos del query string. Este DTO contiene unas reglas de validación que nos aporta class-validator.

import { Type } from "class-transformer";
import { IsInt, IsOptional, IsString, Matches,  } from "class-validator";

export class QueryProductDto {
  @IsInt()
  @Type(() => Number)
  @IsOptional()
  limit: number;

  @Matches(/^(stock|name)$/)
  @IsString()
  @IsOptional()
  order: string;

  @IsString()
  @IsOptional()
  name: string;
}

Hemos validado lo siguiente:

Recibiendo los datos en el controlador y seteando valores predeterminados

En el controlador podemos recibir todos los datos que queremos validar mediante @query, igual que antes. Sin embargo, ahora vamos a ayudarnos del DTO para que se realicen las validaciones.

Adicionalmente, vamos a crear unas opciones predeterminadas y las mezclaremos con las opciones que nos lleguen en el objeto de query.

@Get()
async getAllProducts(@Query(new ValidationPipe()) query: QueryProductDto): Promise<Product[]> {
  const mergedQuery = { 
    limit: 5,
    order: 'name',
    name: '',
    ...query
  };
  return this.productsService.getAll(mergedQuery);
}

Como puedes ver, gracias a ValidationPipe conseguimos que se apliquen todas las reglas de validación de QueryProductDto.

No te olvides de importar todas las declaraciones de los elementos que venimos usando!!

Usando el objeto query en el servicio

El uso del objeto de consulta dentro del servicio es bien sencillo, ya que hemos tomado la precaución de validarlo todo y aplicar valores por defecto a todas las propiedades de la consulta.

Así que solamente se trata de aplicar las propiedades correspondientes del objeto de opciones en find().

getAll(query: QueryProductDto): Promise<Product[]> {
  return this.productsRepository.find({
    take: query.limit,
    where: {
      name: Like(`%${query.name}%`),
    },
    order: {
      [query.order]: 'ASC',
    },
  });
}

La verdad es que es sorprendente la cantidad de trabajo que te ahorra Nest para hacer validaciones y consultas sobre la base de datos.

Miguel Angel Alvarez

Fundador de DesarrolloWeb.com y la plataforma de formación online EscuelaIT. Com...

Manual