Validando y transformando solicitudes en NestJS

Validando y transformando solicitudes en NestJS

NestJS ha ganado popularidad en el mundo del desarrollo backend gracias a su robustez y enfoque modular. Aunque es una herramienta poderosa, como cualquier otra tecnología, no está exenta de desafíos, especialmente para los nuevos usuarios. Uno de los problemas más comunes al trabajar con NestJS involucra la validación y transformación de solicitudes utilizando librerías como class-validator y class-transformer.

Aún teniendo algo de experiencia con este frameworks, de vez en cuando se olvidan pequeños detalles que pueden darnos problemas o hacernos perder el tiempo y uno de ellos, específicamente pasa cuando se reciben peticiones tipo POST que al tener propiedades anidadas, puede que aparentemente no veamos el objeto completo, pero esto tiene una explicación y de eso se trata este artículo.


El problema: Propiedades ausentes o no validadas

Imagina que estás desarrollando una API que acepta detalles del dispositivo de un usuario. La solicitud POST podría verse así:

{
    "deviceName": "GalaxyS21",
    "deviceId": "a1b2c3d4-e5f6-7g8h-9i0j-k1l2m3n4o5p6",
    "specs": {
        "os": "android",
        "osVersion": "11",
        "resolution": "2400x1080",
        "appVersion": "2.1.0"
    }
}

Sin embargo, después de enviar la solicitud, te das cuenta de que la propiedad specs no aparece en el servidor o, peor aún, la solicitud falla sin razón aparente.


Paso a paso: Diagnóstico y Solución

  1. Define correctamente tus DTOs:

Un DTO, o Data Transfer Object, es un patrón de diseño adoptado en la programación orientada a objetos. En NestJS, un DTO se utiliza principalmente para definir la forma y estructura de los datos que esperas recibir en las solicitudes (como POST o PUT). Los DTOs proporcionan una forma clara de especificar qué datos se pueden enviar o recibir, ofreciendo un contrato entre el cliente y el servidor, lo que ayuda a mantener la integridad y consistencia de los datos.

Asegúrate de que tu DTO refleje correctamente la estructura de la solicitud. Por ejemplo:

class DeviceSpecsDTO {
   @IsString()
   os: string;

   @IsString()
   osVersion: string;

   @IsString()
   resolution: string;

   @IsString()
   appVersion: string;
}

class DeviceDTO {
   @IsString()
   deviceName: string;

   @IsString()
   @IsUUID()
   deviceId: string;

   // Importante: indicar que es un objeto anidado
   @Type(() => DeviceSpecsDTO)
   @IsObject()
   @ValidateNested()
   specs: DeviceSpecsDTO;
}

Un error común que puede surgir al definir DTOs anidados en NestJS se relaciona con la omisión del decorador @IsObject(). Imagina que tienes una propiedad anidada llamada specs de tipo DeviceSpecsDTO en tu DTO principal. Si no utilizas el decorador @IsObject() para indicar explícitamente que specs es un objeto, al enviar una solicitud y procesar el payload, te encontrarás con que propiedades como deviceName y deviceId se reconocen correctamente, pero sorprendentemente, specs no aparecerá o no se procesará como esperabas. Es esencial ser consciente de estos detalles al trabajar con DTOs anidados para asegurar una correcta validación y transformación de datos.

Al hablar de DTOs en NestJS, es común mencionar dos librerías importantes: class-transformer y class-validator.

  • class-transformer: Esta librería nos permite transformar objetos planos en instancias de clases y viceversa. Es especialmente útil cuando se desea convertir el cuerpo de una solicitud (un objeto plano) en una instancia de DTO para luego hacer operaciones con él como si fuera una instancia regular de una clase. El decorador @Type() es uno de los decoradores proporcionados por class-transformer que indica cómo transformar una propiedad específica.

  • class-validator: Como su nombre indica, esta librería se utiliza para validar que las instancias de objetos cumplan con ciertos criterios. En el contexto de NestJS, se utiliza para asegurarse de que las solicitudes entrantes cumplan con las expectativas antes de que se procesen. Los decoradores como @IsString() o @IsUUID() son ejemplos de cómo se puede establecer criterios de validación para las propiedades de un DTO.

  1. Habilita la Transformación Global:

IMPORTANTE: Es crucial habilitar la transformación global para que class-transformer pueda realizar su trabajo. En el archivo main.ts:

app.useGlobalPipes(new ValidationPipe({
  transform: true, // esto es lo que debes considerar agregar
  whitelist: true,
  forbidNonWhitelisted: true,
}));

Con transform: true, NestJS transformará automáticamente los objetos planos entrantes en instancias de clase, permitiendo la validación adecuada y la transformación.

  1. Logging y Depuración:

Usa logs para inspeccionar qué está llegando al controlador. Esto te ayudará a identificar si el problema ocurre antes de la validación o después:

@Post('register-device')
registerDevice(@Body() data: DeviceDTO) {
   console.log(data);
   // Lógica del controlador aquí
}

Si observas que en la consola, el objeto data no contiene la propiedad specs puede que hayas olvidado el decorador @IsObject().

  1. Validación Manual:

Si sospechas que hay un problema específico con la validación automática, puedes deshabilitarla temporalmente y manejarla manualmente, lo único que necesitarías para deshabilitarla sería quitar el tipo de dato que esperas recibir y utilizar any:

@Post('register-device')
registerDevice(@Body() data: any) {
   const deviceDtoInstance = plainToClass(DeviceDTO, data);
   const errors = validateSync(deviceDtoInstance);
   console.log(errors);
   // Lógica del controlador aquí
}

Conclusión

Trabajar con NestJS es gratificante, pero, como con cualquier herramienta de desarrollo, es importante conocer bien cómo funciona. La validación y transformación de datos son fundamentales para mantener una API fuerte y segura. Espero que este artículo te haya ayudado a entender mejor cómo NestJS gestiona estos aspectos, los posibles obstáculos que podrías encontrar y cómo superarlos.

Happy coding! :D


Photo by Scott Webb on Unsplash

Jack Fiallos

Jack Fiallos

Te gustó este artículo?