Publicado en

Nestjs uso de Validator y Transformers

Sin validaciones, un usuario puede enviar cualquier dato a tu API — un email inválido, una edad negativa, o campos vacíos — y llegarían directo a la base de datos. class-validator y class-transformer resuelven esto antes de que el request llegue al servicio

Descripción

class-transform

Se encarga de cambiar al tipo correcto de dato.

En el servicio no se ven por que hacen su trabajo antes de que llegue el request ahí. El servicio recibe los datos ya validados y transformados.

Donde actúan los transformers en el Param(‘id’) del controlador.

// Sin transform: true
@Get(':id')
findOne(@Param('id') id: string) {  // id llega como STRING "1"
  return this.usuarioService.findOne(+id); // necesitas el +id para convertir
}

// Con transform: true puedes hacer esto:
@Get(':id')
findOne(@Param('id', ParseIntPipe) id: number) { // id ya llega como NUMBER
  return this.usuarioService.findOne(id); // sin el +
}

El @body tambien convierte los tipos automaticamente

// Si el DTO tiene edad: number
// y el cliente manda: { "edad": "25" }  ← string
// transform: true lo convierte a número automáticamente antes de llegar al servicio

Se coloco un transform explicito dentro del DTO create (CreateUsuarioDto)

@Type(() => Number)

class-validator

Valida que los datos sean los correctos.

ValidationPipe

Se encarga de orquestar ambos.

Vamos a utilizar el validator y transformers.

Lo primero hay que instalar las dependencias.

npm install class-validator class-transformer

voy a modificar el archivo principal main.ts importante los ValidationPipe y agregando unas lineas para su uso.

import { ValidationPipe } from '@nestjs/common'; // Validator

  app.useGlobalPipes(new ValidationPipe({
    whitelist: true, // Elimina los campos que no estan en los DTOs
    forbidNonWhitelisted: true, // Lanza un error si llegan campos extra
    transform: true,  // Transforma tipos automaticamente.
  }));

Sin whitelist considera que el campo extra llega al servicio
Con whitelist: true considera que campo extra se elimina silenciosamente
Con forbidNonWhitelisted: true >> lanza error 400

Quedando de esta manera.

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { ValidationPipe } from '@nestjs/common'; // Validator

async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  app.useGlobalPipes(new ValidationPipe({
    whitelist: true, // Elimina los campos que no estan en los DTOs
    forbidNonWhitelisted: true, // Lanza un error si llegan campos extra
    transform: true,  // Transforma tipos automaticamente.
  }));

  await app.listen(process.env.PORT ?? 3000);
}
bootstrap();

He generado una entidad llamada Usuario para poder hacer las validaciones.

nest g resource Usuario

Entonces vamos a utilizar arrays en memoria dado que no me he conectado a una base de datos prisma.

// Usaremos arrays en memoria, dado que no conecte a prisma
export class Usuario {
    id!: number;
    nombre!: string;
    email!: string;
    edad!: number;
}

DTOs (Simple sin validaciones)

export class CreateUsuarioDto {
    nombre!: string;
    email!: string;
    edad!: number;    
}

Ahora le voy a incluir las validaciones en una primera version.

import { IsEmail, IsNotEmpty, IsString, IsNumber, Min, Max } from 'class-validator';
import { Type } from 'class-transformer';

export class CreateUsuarioDto {

    @IsNotEmpty({message: 'El nombre es obligatorio'})
    @IsString()
    nombre!: string;

    @IsNotEmpty()
    @IsEmail({}, {message: 'Email inválido'})
    email!: string;

    @IsNumber()
    @Min(18, {message: 'Debe de ser mayor de 18'})
    @Max(99)
    @Type( () => Number) //Aqui se utiliza la clase transform explicitamente.
    edad!: number;
}
import { PartialType } from '@nestjs/mapped-types';
import { CreateUsuarioDto } from './create-usuario.dto';

export class UpdateUsuarioDto extends PartialType(CreateUsuarioDto) {}

Se define el servicio.

import { Injectable, NotFoundException } from '@nestjs/common';
import { CreateUsuarioDto } from './dto/create-usuario.dto';
import { UpdateUsuarioDto } from './dto/update-usuario.dto';
import { Usuario } from './entities/usuario.entity'; // Importar entity

@Injectable()
export class UsuarioService {

  // Array en memoria como una base de datos temporal
  private usuarios: Usuario[] = []
  private idCounter = 1;

  create(data: CreateUsuarioDto): Usuario {
    const usuario: Usuario = {
      id: this.idCounter++,
      ...data,
    };
    this.usuarios.push(usuario);
    return usuario;
  }

  findAll(): Usuario[] {
    return this.usuarios;
  }

  findOne(id: number): Usuario {
    const usuario = this.usuarios.find(u => u.id === id);
    if (!usuario) throw new NotFoundException(`Usuario ${id} no encontrado`);
    return usuario;
  }

  update(id: number, data: UpdateUsuarioDto): Usuario {
    const usuario = this.findOne(id);
    Object.assign(usuario, data) // Actualiza solo los campos enviados
    return usuario;
  }

  remove(id: number): {message: string} {
    const index = this.usuarios.findIndex(u => u.id ===id);
    if (index === -1) throw new NotFoundException(`Usuario ${id} no encontrado`);
    this.usuarios.splice(index, 1);
    return {message: `Usuario ${id} eliminado`};
  }
  
}

En el controlador y el modulo, no hay mayor cambio.

Formas de validar la operaciones CRUD de Usuario.

# Crear
Invoke-WebRequest -Uri http://localhost:3000/usuario -Method POST -Headers @{"Content-Type"="application/json"} -Body '{"nombre":"Jeff","email":"jeff@gmail.com","edad":25}' | Select-Object -ExpandProperty Content

# Listar
Invoke-WebRequest -Uri http://localhost:3000/usuario -Method GET | Select-Object -ExpandProperty Content | ConvertFrom-Json

# Actualizar
Invoke-WebRequest -Uri http://localhost:3000/usuario/1 -Method PATCH -Headers @{"Content-Type"="application/json"} -Body '{"nombre":"Jeff Santillan"}' | Select-Object -ExpandProperty Content

# Eliminar
Invoke-WebRequest -Uri http://localhost:3000/usuario/1 -Method DELETE | Select-Object -ExpandProperty Content

Validaciones

Error por ingreso de edad superior a 99

Mensaje de error por que no se ingreso un nombre:

Mensaje de error por un email no valido.

Se logra la validación de manera exitosa.

{
  "id": 1,
  "nombre": "Jeff",
  "email": "jeff@gmail.com",
  "edad": 25
}

¡Saludos!

Un comentario en «Nestjs uso de Validator y Transformers»

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *