> Manuales > Manual de NestJS

Qué son los pipes en Nest y cómo ayudan a mantener un código limpio en los controladores, evitando código repetitivo para realizar validaciones y transformaciones. Ejemplos del pipe ParseIntPipe y personalización de excepciones.

Pipes en Nest

Los pipes en Nest son uno de los elementos más usados y nos permiten realizar cómodamente validaciones y transformaciones. Entran dentro de la clasificación de los providers y por lo tanto son inyectables.

Existen multitud de pipes ya disponibles en el framework y, naturalmente, nosotros podemos crear pipes personalizados. Sus usos son básicamente los siguientes:

Para usar un pipe tenemos que utilizar decoradores en las firmas de los métodos de controlador que se ejecutan para implementar las rutas de aplicación. En seguida veremos un ejemplo que lo dejará muy claro.

Gracias a Nest, los pipes se ejecutan antes del propio método del controlador, pudiendo trabajar con sus argumentos, para validarlos o realizar cualquier transformación. Una vez ejecutado el proceso del pipe pueden pasar dos cosas:

En artículos anteriores del Manual de Nest ya explicamos cómo el framework trabaja con las excepciones, proporcionando directamente convenientes salidas a los clientes. Más información en el artículo Gestión de errores en Nest Framework mediante excepciones

Pipes disponibles en el framework

Existen diversos pipes para realizar operativas habituales en las aplicaciones. Uno de ellos es ValidationPipe, que permite realizar todas las validaciones necesarias sobre los datos de entrada de las aplicaciones, lo que resulta muy cómodo para evitar la mayoría del código necesario cuando se desea comprobar que los datos son correctos, antes de operar con ellos.

Pero también hay otros pipes como ParseIntPipe o ParseBoolPipe, más elementales pero también extremadamente útiles, que realizan las transformaciones adecuadas para proporcionar datos en el tipo que necesitamos dentro de los métodos de los controladores. Si no pueden realizar esas transformaciones, entonces levantarán igualmente excepciones.

Por supuesto, en la documentación de Nest encontramos toda una descripción de los pipes disponibles. En el presente artículo vamos a introducirnos en el uso de pipes tomando como ejemplo a ParseIntPipe, creando nuestros primeros ejemplos de uso de estos artefactos.

ParseIntPipe

Este pipe sirve para transformar un dato a su valor integer. Es por tanto un pipe de transformación, sin embargo, en el fondo también realiza una validación, puesto que si no es un valor transformable en un entero, simplemente levantará una excepción por nosotros y, por tanto, se devolverá un error al cliente.

Un uso típico de ParseIntPipe se da cuando intentamos acceder a una ruta del estilo:

http://example.com/products/22

Se supone que el último valor de la ruta de un producto, el id "22", debería de ser un identificador de producto válido, cuyo valor siempre sería un número entero. Si la ruta contiene cualquier cosa que no sea un entero, por ejemplo http://example.com/products/xxx, debería fallar de manera amistosa para el cliente (en vez de explotar la aplicación), entregando un conveniente mensaje de error y un status code 400 (bad request) o similar.

Hasta ahora, en el proyecto que hemos ido realizando, no hacíamos nada en especial para verificar los datos que llegaban al controlador y teníamos un código como este:

@Get(':id')
find(@Param('id') id: number) {
  return this.productsService.getId(id);
}

¿Pero qué crees que pasaría si el "id" recibido como parámetro no es un entero? probablemente nuestra aplicación reventase cuando intentemos localizar en la base de datos ese producto con un id no válido. Así que ParseIntPipe nos puede ayudar bastante a producir un comportamiento deseable para este tipo de situaciones.

El pipe lo usaremos en la declaración de un método, dentro de su firma.

@Get(':id')
async find(@Param('id', ParseIntPipe) id: number) {
  return this.productsService.getId(id);
}

Simplemente, cuando extraemos el parámetro con el decorador @Param, estamos entregando como segundo parámetro la clase ParseIntPipe. Nest se encargará de hacer todo el trabajo de transformación y levantado de una posible excepción.

Además, habrás notado que hemos declarado el método como async. Esto es debido a que algunos de los pipes pueden trabajar devolviendo promesas, de modo que así nos aseguramos el correcto funcionamiento de nuestra ruta.

Por supuesto, no te puedes olvidar de importar ParseIntPipe desde "@nestjs/common".

import { Get, Param, ParseIntPipe } from '@nestjs/common';

Ahora, si hacemos una ruta correcta, como por ejemplo http://example.com/products/1, el pipe transformará en entero el valor "id", que es justamente lo que necesita el método del servicio. En este caso el flujo de ejecución del método se realizará correctamente, ejecutándose el cuerpo del método del controlador para gestionar la solicitud.

Por contra, si intentamos acceder a una ruta que no era correcta, como por ejemplo http://example.com/products/xyz el pipe se encargará de levantar una excepción. Gracias a ello no tenemos que preocuparnos por validar nada y tampoco tratar los posibles mensajes de error de validación. La salida que nos entregará Nest será algo como esto:

{
    "statusCode": 400,
    "message": "Validation failed (numeric string is expected)",
    "error": "Bad Request"
}

Todo lo que estamos explicando sobre ParseIntPipe lo puedes aplicar a todos los pipe de transformación que Nest te proporciona. No vemos en este artículo ejemplos de ellos, pero probablemente los tendrás más adelante en este manual.

Usar una excepción personalizada con el pipe de transformación

En el ejemplo anterior dejamos a Nest la responsabilidad de crear una instancia de ParseIntPipe para realizar la transformación. Sin embargo, nosotros también podemos realizar explícitamente esa instanciación indicando cierta configuración.

Supongamos que queremos levantar una excepción con un status code personalizado. Esto es perfectamente posible enviando el código en un objeto de configuración que recibe el constructor de ParseIntPipe.

En el siguiente código puedes ver cómo enviaríamos un status code 406 (Not Acceptable), en lugar del status code 400 como ocurría antes.

@Put(':id')
async update(
  @Param('id', new ParseIntPipe({ errorHttpStatusCode: HttpStatus.NOT_ACCEPTABLE })) id: number, 
  @Body() body,
) {
  return this.productsService.update(id, body);
}

En este caso, si enviamos un dato incorrecto en el identificador en la solicitud, recibiremos una respuesta como esta:

{
    "statusCode": 406,
    "message": "Validation failed (numeric string is expected)",
    "error": "Not Acceptable"
}

De nuevo, el pipe nos permite hacer la transformación del dato y evitar cualquier posible situación derivada de una request mal formada. Además, el código queda perfectamente limpio, ya que no es necesaria ninguna validación explícita en el cuerpo del método y es el framework el que se encarga de hacer toda la parte pesada y tediosa.

En los próximos artículos veremos otros tipos de pipes también muy útiles, como es el ValidationPipe. Las validaciones las haremos muy fácilmente gracias a un tipo de clases nuevo que no hemos explicado todavía y que veremos en el próximo artículo: Los DTO en aplicaciones Nest.

Miguel Angel Alvarez

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

Manual