Engenharia de IA Aplicada à Programação Web
Aula 4 de 8
Navegar por módulos e aulas
  1. 01 Fundamentos da Web
  2. 02 JavaScript moderno
  3. 03 TypeScript, Git e qualidade
  4. 04 Angular
  5. 05 Node.js, NestJS e APIs
  1. 5.1 Introdução ao Node.js e NestJS
  2. 5.2 Módulos, controladores e provedores
  3. 5.3 REST, rotas e respostas HTTP
  4. 5.4 DTOs, pipes e validação
  5. 5.5 Erros, filtros e configuração
  6. 5.6 OpenAPI e documentação
  7. 5.7 Registros, interceptadores e testes
  8. 5.8 Compilação e projeto final

Módulo 5 · Aula 5.4

Aula 5.4 — DTOs, pipes e validação

2 horas Teoria + laboratório Node.js, NestJS e APIs
Ver fonte Markdown

Identificação

  • Módulo: 5 — Node.js, NestJS e APIs
  • Duração: 2 horas
  • Tipo: teoria aplicada e laboratório
  • Entrega: endpoints protegidos por DTOs e ValidationPipe global
  • Laboratório: ../exemplos/aula-5.4/index.html

Introdução

Na aula anterior construímos endpoints REST. Porém, qualquer pessoa pode enviar dados diferentes do que nosso código espera:

{
  "title": 42,
  "completed": "talvez",
  "isAdmin": true
}

Escrever title: string em TypeScript não impede que esse JSON chegue pela rede. Tipos TypeScript ajudam durante o desenvolvimento, mas são apagados quando o código é transformado em JavaScript.

Nesta aula criaremos uma fronteira de entrada:

requisição bruta
→ transformação
→ remoção ou rejeição de campos desconhecidos
→ validação
→ controller

Perguntas que responderemos:

  • DTO é a mesma coisa que entidade do banco?
  • uma interface TypeScript valida JSON?
  • pipe é um cano físico ou um operador do terminal?
  • "false" recebido na URL já é um booleano falso?
  • whitelist e forbidNonWhitelisted fazem a mesma coisa?
  • validação elimina a necessidade de regras de negócio?

Objetivos

Ao concluir a aula, você será capaz de:

  1. explicar DTO, pipe, transformação e validação;
  2. diferenciar tipagem estática de validação em ambiente de execução;
  3. criar DTOs como classes concretas;
  4. usar decorators do class-validator;
  5. configurar ValidationPipe global;
  6. aplicar whitelist, forbidNonWhitelisted e transform;
  7. criar DTOs específicos para corpo, query e parâmetros;
  8. usar PartialType() para atualizações;
  9. diferenciar validação estrutural de regra de negócio;
  10. devolver erros 400 compreensíveis e seguros.

Pré-requisitos

  • aulas 5.1 a 5.3 concluídas;
  • noções de classe e decorator em TypeScript;
  • endpoints REST do TasksModule;
  • saber que corpo, query e parâmetros são dados externos.

1. O que é DTO?

DTO significa Data Transfer Object, ou objeto de transferência de dados. Ele descreve o formato que atravessa uma fronteira, como a entrada de uma API.

export class CreateTaskDto {
  title!: string;
}

O DTO de criação não precisa possuir id, pois o servidor cria esse valor. Também não precisa ser a mesma classe usada pelo ORM ou devolvida na resposta.

2. DTO não é entidade

DTO de entrada Entidade ou modelo persistido
contrato da API estado interno do domínio ou banco
controla o que o cliente envia pode possuir campos privados
muda com a operação muda com as regras e persistência
não deve expor detalhes do MySQL representa dados internos

Reutilizar a entidade do banco como corpo pode permitir alterações em campos que o cliente não deveria controlar.

3. Por que usar classe em vez de interface?

interface CreateTaskDto {
  title: string;
}

A interface desaparece em ambiente de execução. Uma classe continua existindo no JavaScript e pode ser identificada pelo ValidationPipe. Por isso DTOs validados devem ser classes concretas.

Não use importação somente de tipo para um DTO validado:

// correto: a classe permanece disponível em runtime
import { CreateTaskDto } from './dto/create-task.dto';

// incorreto para o metadado de validação
import type { CreateTaskDto } from './dto/create-task.dto';

4. O que é um pipe?

No NestJS, um pipe recebe um valor antes do controller e pode:

  1. transformá-lo;
  2. validá-lo;
  3. devolver o valor aceito;
  4. ou lançar uma exceção.

Se o pipe lança uma exceção, o método do controller não é executado.

5. Dependências necessárias

npm install class-validator class-transformer

Para mapped types usados neste exemplo:

npm install @nestjs/mapped-types

6. Primeiro DTO validado

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

export class CreateTaskDto {
  @IsString({ message: 'title deve ser texto.' })
  @MinLength(3, { message: 'title deve possuir ao menos 3 caracteres.' })
  @MaxLength(80, { message: 'title deve possuir no máximo 80 caracteres.' })
  title!: string;
}

Os decorators não são comentários. Eles registram metadados que o validador usa em ambiente de execução.

7. Transformando o título

import { Transform } from 'class-transformer';

@Transform(({ value }) => typeof value === 'string' ? value.trim() : value)
@IsString()
@MinLength(3)
@MaxLength(80)
title!: string;

Transformamos apenas quando o valor já é string. Converter qualquer coisa com String(value) faria 42 virar "42" e poderia esconder uma entrada inválida.

8. Configuração global

app.useGlobalPipes(
  new ValidationPipe({
    transform: true,
    whitelist: true,
    forbidNonWhitelisted: true,
    transformOptions: {
      enableImplicitConversion: false,
    },
  }),
);

Aplicar globalmente cria uma política consistente para todos os endpoints.

9. O que faz transform?

Requisições chegam como objetos JavaScript simples. Com transform: true, o pipe pode transformá-los em instâncias das classes esperadas e aplicar transformações declaradas.

Isso não significa que toda conversão automática é segura. A string "false", quando convertida genericamente com Boolean("false"), resulta em true. Prefira conversões explícitas para booleanos externos.

10. O que faz whitelist?

Com whitelist: true, propriedades sem decorator de validação são removidas:

{
  "title": "Estudar pipes",
  "isAdmin": true
}

Depois da lista de permissões:

{
  "title": "Estudar pipes"
}

Para um campo permitido permanecer, ele deve possuir ao menos um decorator adequado.

11. O que faz forbidNonWhitelisted?

Com whitelist: true e forbidNonWhitelisted: true, a propriedade desconhecida não é apenas removida: a requisição é rejeitada com 400 Bad Request.

Essa política ajuda o cliente a perceber que enviou um contrato incorreto e reduz tentativas de mass assignment.

12. DTO de atualização

Na criação, title é obrigatório. Na atualização parcial, os campos são opcionais.

import { PartialType } from '@nestjs/mapped-types';
import { IsBoolean, IsOptional } from 'class-validator';
import { CreateTaskDto } from './create-task.dto';

export class UpdateTaskDto extends PartialType(CreateTaskDto) {
  @IsOptional()
  @IsBoolean({ message: 'completed deve ser booleano.' })
  completed?: boolean;
}

PartialType() produz uma classe derivada e preserva os metadados necessários. Não é o mesmo que Partial<CreateTaskDto>, que existe apenas no sistema de tipos.

13. @IsOptional()

@IsOptional() ignora os validadores seguintes quando o valor é null ou undefined. Ele não transforma string vazia em ausência.

Em updates, diferencie:

  • campo omitido: não alterar;
  • campo enviado com false: alterar para falso;
  • campo enviado vazio: validar conforme o contrato.

14. Query DTO

Cadeias de consulta chegam como texto:

GET /api/tasks?completed=false&search=nest
export class ListTasksQueryDto {
  @IsOptional()
  @Transform(({ value }) => {
    if (value === 'true') return true;
    if (value === 'false') return false;
    return value;
  })
  @IsBoolean({ message: 'completed deve ser true ou false.' })
  completed?: boolean;

  @IsOptional()
  @IsString()
  @MaxLength(50)
  search?: string;
}

O valor inválido completed=talvez continua como string e falha em @IsBoolean().

15. DTO de parâmetros

Como nossos IDs de exemplo seguem task-1, podemos validar o padrão:

export class TaskParamsDto {
  @Matches(/^task-\d+$/, {
    message: 'id deve seguir o formato task-N.',
  })
  id!: string;
}

Em aplicações que usam UUID, o NestJS oferece ParseUUIDPipe e o class-validator oferece @IsUUID().

16. Pipes prontos

O NestJS inclui pipes como:

Pipe Função
ParseIntPipe converte e valida inteiro
ParseBoolPipe converte e valida booleano
ParseUUIDPipe valida UUID
ParseEnumPipe valida membro de enum
ParseArrayPipe interpreta e valida arrays
DefaultValuePipe fornece valor padrão
@Get(':id')
findOne(@Param('id', ParseIntPipe) id: number) {}

Ou o valor chega convertido, ou uma exceção impede o controller de executar.

17. Controller com DTOs

@Post()
create(@Body() input: CreateTaskDto): Task {
  return this.tasksService.create(input.title);
}

@Get()
list(@Query() query: ListTasksQueryDto): Task[] {
  return this.tasksService.list(query);
}

@Patch(':id')
update(
  @Param() params: TaskParamsDto,
  @Body() input: UpdateTaskDto,
): Task {
  return this.tasksService.update(params.id, input);
}

O controller recebe dados já transformados e estruturalmente válidos.

18. Validação estrutural versus regra de negócio

DTO valida formato e limites da entrada:

  • title é string;
  • possui entre 3 e 80 caracteres;
  • completed é booleano.

Service valida regras de negócio:

  • usuário pode editar esta tarefa?
  • tarefa arquivada pode ser reaberta?
  • título já existe dentro deste projeto?

Não tente colocar toda a lógica de negócio em decorators.

19. Ordem conceitual

middleware
→ guards
→ interceptors antes
→ pipes
→ controller
→ service
→ interceptors depois
→ filtros de exceção quando necessário

Nesta aula focamos o trecho em que pipes protegem os argumentos do controller.

20. Resposta de validação

Uma entrada inválida pode produzir:

{
  "statusCode": 400,
  "message": [
    "title deve possuir ao menos 3 caracteres."
  ],
  "error": "Bad Request"
}

Mensagens devem orientar o cliente sem revelar stack trace, caminhos internos ou segredos.

21. Propriedades aninhadas

Para validar objetos internos, não basta decorar apenas o objeto externo:

export class MetadataDto {
  @IsString()
  source!: string;
}

export class ImportTaskDto {
  @ValidateNested()
  @Type(() => MetadataDto)
  metadata!: MetadataDto;
}

@Type() informa ao class-transformer qual classe concreta deve ser criada.

22. Arrays

export class BulkCreateTasksDto {
  @IsArray()
  @ArrayMinSize(1)
  @ValidateNested({ each: true })
  @Type(() => CreateTaskDto)
  items!: CreateTaskDto[];
}

Também é possível usar ParseArrayPipe para arrays recebidos diretamente.

23. Evitando conversão implícita perigosa

Considere:

completed=false

Uma conversão genérica baseada no construtor Boolean pode gerar true, porque a string não está vazia. Neste curso, booleanos externos são convertidos explicitamente.

24. Atualização vazia

UpdateTaskDto torna campos opcionais, então {} pode passar pela validação estrutural. Se uma atualização vazia não fizer sentido, essa é uma regra adicional:

if (Object.keys(input).length === 0) {
  throw new BadRequestException('Informe ao menos um campo para atualização.');
}

Ela pode estar em um validator de classe ou no service, dependendo da arquitetura.

25. Validação não é sanitização universal

Validar comprimento não torna texto automaticamente seguro em todos os contextos.

  • HTML deve ser exibido como texto quando não for conteúdo confiável;
  • SQL deve usar parâmetros ou ORM;
  • registros precisam evitar quebra e dados sensíveis;
  • URLs e nomes de arquivo exigem regras próprias.

26. Configuração recomendada para o curso

new ValidationPipe({
  transform: true,
  whitelist: true,
  forbidNonWhitelisted: true,
  validationError: {
    target: false,
    value: false,
  },
  transformOptions: {
    enableImplicitConversion: false,
  },
})

Ocultar target e value reduz a chance de refletir dados desnecessários na resposta.

27. XAMPP, Angular e MySQL

O fluxo permanece:

Angular → JSON → ValidationPipe → controller → service → repositório → MySQL

O Apache do XAMPP pode servir o curso e a interface. A validação real executa no processo NestJS. O navegador continua sem acessar MySQL diretamente.

28. Roteiro de implementação

Etapa Tempo sugerido
Tipos versus ambiente de execução 15 min
DTOs e decorators 25 min
ValidationPipe global 20 min
Corpo, query e params 25 min
Regras, erros e segurança 15 min
Laboratório 15 min
Revisão 5 min

29. Laboratório guiado

Abra o Fluxo de validação.

Etapa 1 — Entrada válida

  1. selecione CreateTaskDto;
  2. envie um título válido;
  3. acompanhe transformação, lista de permissões e validação;
  4. confirme que o controller recebe o DTO.

Etapa 2 — Tipo incorreto

  1. altere o tipo do título para número;
  2. execute novamente;
  3. confirme que o controller não é chamado.

Etapa 3 — Campo desconhecido

  1. adicione isAdmin;
  2. teste lista de permissões sem proibição;
  3. depois ative forbidNonWhitelisted;
  4. compare remoção silenciosa e rejeição explícita.

Etapa 4 — Booleano da query

  1. selecione ListTasksQueryDto;
  2. teste true, false e talvez;
  3. observe tipo antes e depois da transformação.

Etapa 5 — Update parcial

  1. selecione UpdateTaskDto;
  2. omita o título e envie completed: false;
  3. confirme que ausência e falso não são confundidos.

30. Erros comuns

Usar interface para DTO validado

Ela desaparece antes da aplicação executar.

Importar DTO com import type

Remove a referência necessária em ambiente de execução.

Confiar apenas em transform: true

Transformação não substitui regras explícitas.

Ativar lista de permissões sem decorators

Campos sem decorators podem ser removidos, mesmo que existam na classe.

Converter booleano com Boolean(value)

Boolean("false") produz true.

Usar o mesmo DTO em todas as operações

Criação, atualização, filtros e parâmetros possuem contratos diferentes.

31. Exercício de fixação

Crie DTOs para documentos:

  • CreateDocumentDto com título de 3 a 120 caracteres;
  • UpdateDocumentDto parcial;
  • ListDocumentsQueryDto com search e limit;
  • DocumentParamsDto validando o formato do ID;
  • lista de permissões e rejeição de campos desconhecidos.

32. Desafio individual

Implemente importação em lote:

  1. wrapper com propriedade items;
  2. mínimo de 1 e máximo de 20 itens;
  3. validação aninhada de cada CreateTaskDto;
  4. resposta que identifica o índice de cada erro;
  5. limite de tamanho da requisição.

33. Lista de verificação de conclusão

  • Sei por que TypeScript não valida a rede.
  • Diferencio DTO de entidade.
  • Uso classes concretas para DTOs validados.
  • Entendo transformação e validação.
  • Configurei o ValidationPipe global.
  • Sei comparar lista de permissões e proibição.
  • Converto booleanos externos explicitamente.
  • Criei DTOs separados para create, update, query e params.
  • O controller não executa quando o pipe falha.
  • Regras de negócio continuam no service.

34. Rubrica da entrega

Critério Pontos
DTOs como classes 15
Regras declarativas 20
ValidationPipe global 15
Lista de permissões e campos desconhecidos 15
Transformação segura 15
DTOs por operação 10
Erros seguros 10
Total 100

35. Fontes oficiais

Próxima aula

Na Aula 5.5, trataremos exceções de forma consistente, criaremos filtros e carregaremos configurações por ambiente sem expor segredos.