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
ValidationPipeglobal - 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?whitelisteforbidNonWhitelistedfazem a mesma coisa?- validação elimina a necessidade de regras de negócio?
Objetivos
Ao concluir a aula, você será capaz de:
- explicar DTO, pipe, transformação e validação;
- diferenciar tipagem estática de validação em ambiente de execução;
- criar DTOs como classes concretas;
- usar decorators do
class-validator; - configurar
ValidationPipeglobal; - aplicar
whitelist,forbidNonWhitelistedetransform; - criar DTOs específicos para corpo, query e parâmetros;
- usar
PartialType()para atualizações; - diferenciar validação estrutural de regra de negócio;
- devolver erros
400compreensí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:
- transformá-lo;
- validá-lo;
- devolver o valor aceito;
- 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
- selecione
CreateTaskDto; - envie um título válido;
- acompanhe transformação, lista de permissões e validação;
- confirme que o controller recebe o DTO.
Etapa 2 — Tipo incorreto
- altere o tipo do título para número;
- execute novamente;
- confirme que o controller não é chamado.
Etapa 3 — Campo desconhecido
- adicione
isAdmin; - teste lista de permissões sem proibição;
- depois ative
forbidNonWhitelisted; - compare remoção silenciosa e rejeição explícita.
Etapa 4 — Booleano da query
- selecione
ListTasksQueryDto; - teste
true,falseetalvez; - observe tipo antes e depois da transformação.
Etapa 5 — Update parcial
- selecione
UpdateTaskDto; - omita o título e envie
completed: false; - 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:
CreateDocumentDtocom título de 3 a 120 caracteres;UpdateDocumentDtoparcial;ListDocumentsQueryDtocomsearchelimit;DocumentParamsDtovalidando o formato do ID;- lista de permissões e rejeição de campos desconhecidos.
32. Desafio individual
Implemente importação em lote:
- wrapper com propriedade
items; - mínimo de 1 e máximo de 20 itens;
- validação aninhada de cada
CreateTaskDto; - resposta que identifica o índice de cada erro;
- 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
ValidationPipeglobal. - 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.