Data Transfer Object (DTO) no NestJS: Validação e Transformação de Dados

Posted by

No desenvolvimento de APIs com NestJS, é fundamental garantir que os dados recebidos nas requisições sejam validados e convertidos para os tipos corretos antes de serem utilizados na lógica da aplicação. Para isso, o NestJS oferece uma poderosa combinação entre DTOs (Data Transfer Objects), o pacote class-validator e o class-transformer, permitindo não apenas a validação, mas também a transformação automática dos dados.

Este artigo aborda como validar e transformar diferentes tipos de dados — como string, boolean, data, email, UUID e outros — usando DTOs no NestJS, além de como configurar as ferramentas necessárias para obter resultados mais consistentes e seguros.

O que é um DTO no NestJS

Um DTO (Data Transfer Object) é uma classe responsável por definir a estrutura esperada dos dados em uma requisição HTTP. Ele é amplamente utilizado nos controladores para receber payloads e garantir que os dados estejam corretos antes de serem passados para os serviços.

Exemplo:

export class CreateUserDto {
  name: string;
  email: string;
}

Ao adicionar decoradores do class-validator, podemos validar os campos. Ao ativar a opção de transformação, o NestJS converte automaticamente os valores recebidos para os tipos desejados.

Como Validar Diferentes Tipos de Dados em DTOs

O pacote class-validator permite validar diversos tipos de dados diretamente dentro dos DTOs. Abaixo estão alguns exemplos práticos.

Campo Tipo String

Garante que o valor seja uma string e respeite limites mínimos e máximos de caracteres:

import { IsString, MinLength, MaxLength } from 'class-validator';

@IsString({ message: 'O nome deve ser uma string.' })
@MinLength(3, { message: 'O nome deve ter pelo menos 3 caracteres.' })
@MaxLength(50, { message: 'O nome não pode exceder 50 caracteres.' })
name: string;

Campo Tipo Boolean

Valida que o campo tenha um valor booleano (true ou false):

import { IsBoolean } from 'class-validator';

@IsBoolean({ message: 'O status deve ser verdadeiro ou falso.' })
active: boolean;

Campo Tipo Data

Valide datas no formato ISO e transforme-as automaticamente para o tipo Date:

import { IsDateString } from 'class-validator';

@IsDateString(null, { message: 'A data deve estar no formato ISO (ex: YYYY-MM-DD).' })
date: Date;

Com a transformação ativada, mesmo que a entrada seja uma string, ela será automaticamente convertida para um objeto Date.

Campo Tipo Email

Verifica se o valor é um endereço de e-mail válido:

import { IsEmail } from 'class-validator';

@IsEmail({}, { message: 'O e-mail informado é inválido.' })
email: string;

Campo Tipo UUID

Confirma que o valor é um identificador único universal (UUID), podendo especificar a versão:

import { IsUUID } from 'class-validator';

@IsUUID('4', { message: 'O ID deve ser um UUID versão 4 válido.' })
id: string;

Campo do tipo Array

import { 
  IsArray, 
  ArrayMinSize, 
  ValidateNested 
} from 'class-validator';
import { Type } from 'class-transformer';
import { ProductDto } from './product.dto';

export class CreateOrderDto {
  @IsArray()
  @ArrayMinSize(1, { message: 'Pelo menos 1 produto é obrigatório.' })
  @ValidateNested({ each: true })
  @Type(() => ProductDto)
  products: ProductDto[];
}

Confirma que a array possui objetos com valores esperado, podendo verificar também o tamanho da array:

Outros Tipos Comuns

  • Número: @IsNumber()
  • URL: @IsUrl()
  • CPF / CNPJ: Use expressões regulares com @Matches()
  • Telefone: Também pode ser validado com @Matches()
  • Arquivo ou Base64: @IsBase64()

Como Ativar a Transformação Automática de Dados no NestJS

A transformação automática de dados permite que o NestJS converta automaticamente os valores recebidos no corpo das requisições para os tipos especificados nos DTOs. Por exemplo, uma string pode ser convertida para número ou data.

Para ativar essa funcionalidade, instale as dependências necessárias:

npm install class-validator class-transformer

Em seguida, configure o ValidationPipe no arquivo main.ts:

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

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

  app.useGlobalPipes(
    new ValidationPipe({
      whitelist: true,
      forbidNonWhitelisted: true,
      transform: true, // Habilita a transformação automática
      transformOptions: {
        enableImplicitConversion: true, // Permite conversão implícita de tipos
      },
      validationError: {
        target: false,
        value: true,
      },
    }),
  );

  await app.listen(3000);
}

bootstrap();

Com essa configuração:

  • Campos declarados como Date serão automaticamente convertidos.
  • Valores numéricos recebidos como strings são convertidos para números.
  • Campos booleanos também são convertidos corretamente (por exemplo, 'true'true).

Uso do @Type() para Transformação Explícita

Em alguns casos, especialmente ao lidar com objetos aninhados ou listas de objetos, o NestJS não consegue inferir automaticamente o tipo esperado. Nesses cenários, devemos usar o decorator @Type() do pacote class-transformer.

Exemplo 1: Objeto Aninhado

// address.dto.ts
export class AddressDto {
  street: string;
  city: string;
}
// user.dto.ts
import { Type } from 'class-transformer';
import { AddressDto } from './address.dto';

export class UserDto {
  name: string;

  @Type(() => AddressDto)
  address: AddressDto;
}

Exemplo 2: Lista de Objetos

export class UserDto {
  name: string;

  @Type(() => AddressDto)
  addresses: AddressDto[];
}

Exemplo 3: Converter String para Date

import { Type } from 'class-transformer';

export class EventDto {
  @Type(() => Date)
  date: Date;
}

Esse recurso garante que os dados sejam corretamente instanciados como objetos das classes especificadas, aumentando a clareza e robustez da sua aplicação.

Personalizando Mensagens de Erro em Português

Você pode personalizar as mensagens de erro lançadas durante a validação dos DTOs adicionando a propriedade message nos decoradores:

@IsEmail({}, { message: 'E-mail inválido. Exemplo: [email protected]' })
email: string;

Também é possível criar pipes personalizados para tratar erros de forma mais granular:

throw new BadRequestException('Formato de data inválido.');

Além disso, você pode centralizar todas as mensagens de erro em um serviço ou arquivo JSON para facilitar futuras adaptações ou internacionalização.

Como Usar o DTO no Controller

Os DTOs são utilizados diretamente nos métodos dos controladores via o decorator @Body().

// src/controllers/user.controller.ts
import { Controller, Post, Body } from '@nestjs/common';
import { CreateUserDto } from '../dto/create-user.dto';

@Controller('users')
export class UserController {
  @Post()
  create(@Body() createUserDto: CreateUserDto) {
    return {
      message: 'Usuário criado com sucesso',
      data: createUserDto,
    };
  }
}

Se os dados recebidos não forem compatíveis com o DTO, o método create() não será executado. Em vez disso, o NestJS lança automaticamente um erro 400 com detalhes sobre o problema.

Fluxo do DTO no NestJS

O fluxo de execução no NestJS ao usar DTOs é o seguinte:

  1. A requisição HTTP chega ao controller.
  2. O NestJS tenta mapear o corpo da requisição para o tipo definido no DTO.
  3. Se o ValidationPipe estiver ativado:
  • As validações são executadas.
  • Caso haja falhas, o NestJS interrompe a execução e retorna uma resposta de erro imediatamente.
  • Caso contrário, o DTO é populado e repassado ao método do controlador.

Dessa forma, qualquer erro na validação do DTO impede que o método do controlador seja executado, evitando chamadas desnecessárias e mantendo a consistência da API.

Conclusão

Implementar DTOs com validação e transformação de dados no NestJS é uma prática essencial para construir APIs robustas, organizadas e seguras. Através do uso combinado de class-validator e class-transformer, é possível validar e converter automaticamente os tipos de dados recebidos nas requisições, garantindo maior consistência e reduzindo erros na camada de negócios.

Ao seguir essas boas práticas, incluindo o uso do @Type() para transformação explícita, você melhora a qualidade do código, aumenta a clareza dos dados e facilita a manutenção e escalabilidade da sua aplicação.

One comment

Leave a Reply

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *