Engenharia de IA Aplicada à Programação Web
Aula 5 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.5

Aula 5.5 — Erros, filtros e configuraçã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: respostas de erro padronizadas e configuração validada na inicialização
  • Laboratório: ../exemplos/aula-5.5/index.html

Introdução

Uma API precisa funcionar quando tudo está certo e falhar de maneira previsível quando algo dá errado. O cliente precisa receber uma resposta útil; a equipe precisa de informações para investigar; dados internos e segredos não podem escapar.

Também não basta tratar erros durante requisições. A aplicação pode iniciar com uma porta inválida, ambiente desconhecido ou segredo ausente e falhar apenas horas depois. Uma configuração inválida deve interromper a inicialização.

Nesta aula responderemos:

  • todo erro deve virar 500?
  • lançar uma exceção derruba o processo Node.js?
  • filtro de exceção é igual ao filtro de uma lista?
  • devemos devolver stack trace em desenvolvimento?
  • .env pode ser publicado no Git?
  • process.env.PORT é número ou string?
  • XAMPP substitui a configuração da API NestJS?

Objetivos

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

  1. diferenciar erro esperado, falha externa e defeito de programação;
  2. usar exceções HTTP com status coerentes;
  3. criar um formato estável de erro;
  4. implementar um filtro global de exceções;
  5. correlacionar resposta e registro interno;
  6. evitar exposição de stack trace e segredos;
  7. carregar configuração com ConfigModule;
  8. validar ambiente, porta e valores obrigatórios na inicialização;
  9. separar configuração pública de segredo;
  10. explicar diferenças entre middleware, interceptor, pipe e filter.

Pré-requisitos

  • aulas 5.1 a 5.4 concluídas;
  • noções de status HTTP;
  • ValidationPipe global;
  • entendimento inicial do ciclo requisição → controller → service.

1. O que é um erro?

“Erro” pode representar situações diferentes:

Categoria Exemplo Tratamento esperado
entrada inválida título curto 400, orientação ao cliente
regra de negócio duplicidade 409, mensagem controlada
recurso ausente ID inexistente 404
dependência indisponível MySQL fora do ar 503, registro e monitoramento
defeito inesperado acesso a undefined 500, registro interno e resposta genérica

Classificar corretamente evita responder 500 para tudo ou revelar detalhes internos.

2. Exceções HTTP prontas

O NestJS fornece classes como:

throw new BadRequestException('Entrada inválida.');
throw new UnauthorizedException('Autenticação necessária.');
throw new ForbiddenException('Ação não permitida.');
throw new NotFoundException('Tarefa não encontrada.');
throw new ConflictException('Já existe uma tarefa com esse título.');
throw new ServiceUnavailableException('Dependência indisponível.');

Essas exceções carregam semântica HTTP e são tratadas pela camada padrão do framework.

3. Lançar não significa encerrar o processo

Uma exceção lançada dentro do ciclo HTTP é capturada pela camada de exceções do NestJS. A requisição atual recebe uma resposta; o processo continua atendendo outras requisições.

Erros fora de um contexto tratado ou falhas na inicialização podem encerrar o processo — e muitas vezes devem, pois executar em estado inválido é pior que não iniciar.

4. Exceção não deve substituir toda condição

Use retorno normal para resultados esperados dentro da função e exceções para impedir o fluxo quando a operação não pode continuar.

findOne(id: string): Task {
  const task = this.repository.findById(id);
  if (!task) {
    throw new NotFoundException(`Tarefa ${id} não encontrada.`);
  }
  return task;
}

5. Formato padronizado

Definiremos esta resposta:

{
  "statusCode": 404,
  "code": "TASK_NOT_FOUND",
  "message": "Tarefa não encontrada.",
  "path": "/api/tasks/task-99",
  "timestamp": "2026-08-01T12:00:00.000Z",
  "correlationId": "req-a81f"
}

code é estável para o cliente; message é legível; correlationId liga a resposta ao registro interno.

6. O que é um exception filter?

Um exception filter executa quando uma exceção não tratada chega à camada de exceções. Ele pode:

  • identificar o status;
  • escolher o conteúdo seguro da resposta;
  • adicionar timestamp, path e correlação;
  • registrar a falha internamente;
  • esconder detalhes de erros inesperados.

Ele não é um try/catch repetido em cada controller.

7. Filtro global

@Catch()
export class AllExceptionsFilter implements ExceptionFilter {
  constructor(private readonly httpAdapterHost: HttpAdapterHost) {}

  catch(exception: unknown, host: ArgumentsHost): void {
    const { httpAdapter } = this.httpAdapterHost;
    const context = host.switchToHttp();
    const request = context.getRequest();
    const response = context.getResponse();

    const status = exception instanceof HttpException
      ? exception.getStatus()
      : HttpStatus.INTERNAL_SERVER_ERROR;

    httpAdapter.reply(response, buildSafeError(exception, request, status), status);
  }
}

ArgumentsHost permite selecionar o contexto HTTP. O HttpAdapterHost reduz o acoplamento direto com Express ou Fastify.

8. Registrando com APP_FILTER

@Module({
  providers: [
    {
      provide: APP_FILTER,
      useClass: AllExceptionsFilter,
    },
  ],
})
export class AppModule {}

Essa forma permite que o NestJS crie o filtro e injete dependências nele.

9. Extraindo resposta de HttpException

exception.getResponse() pode devolver string ou objeto. O filtro precisa normalizar sem assumir uma única forma.

const detail = exception.getResponse();
const message = typeof detail === 'string'
  ? detail
  : readSafeMessage(detail);

Mensagens de validação podem ser arrays; códigos de negócio podem vir em um objeto controlado.

10. Erro inesperado

Para uma exceção desconhecida:

{
  "statusCode": 500,
  "code": "INTERNAL_ERROR",
  "message": "Ocorreu um erro interno."
}

O cliente não recebe:

  • stack trace;
  • consulta SQL;
  • caminho do servidor;
  • senha, token ou chave;
  • nome de tabela interna.

O registro interno pode conter detalhes necessários, respeitando políticas de dados.

11. Correlação

Um identificador de correlação acompanha uma operação entre camadas e serviços:

resposta:     correlationId=req-a81f
registro API: correlationId=req-a81f
registro BD:  correlationId=req-a81f

Ele ajuda a localizar o evento correto sem mostrar detalhes técnicos ao usuário.

Não use CPF, e-mail ou token como correlation ID. Gere um identificador opaco.

12. Middleware, interceptor, pipe e filter

Componente Papel típico
middleware contexto inicial, correlação, integração com requisição bruta
guard permitir ou negar acesso
interceptor envolver execução, medir tempo, transformar resposta
pipe transformar e validar argumento
filter produzir resposta quando uma exceção escapa

Escolher o ponto correto evita duplicação e efeitos inesperados.

13. Ciclo quando há exceção

requisição
→ middleware de correlação
→ guard
→ interceptor
→ pipe
→ controller
→ service lança exceção
→ filter global
→ resposta segura

Quando um filter trata a exceção, o fluxo normal restante não continua.

14. O que é configuração?

Configuração são valores que mudam entre ambientes sem alterar o código:

NODE_ENV=development
PORT=3000
DATABASE_HOST=localhost
DATABASE_PORT=3306
DATABASE_NAME=knowledge_ai

Uma imagem de produção pode executar com valores diferentes dos usados localmente.

15. Ambiente

Usaremos três ambientes básicos:

Ambiente Objetivo
development desenvolvimento local
test testes automatizados isolados
production operação real e políticas restritas

Ambiente não deve ser inferido por hostname ou pelo fato de usar XAMPP. Declare-o.

16. process.env contém strings

const rawPort = process.env.PORT;

O tipo é string | undefined. Mesmo PORT=3000 chega inicialmente como texto. Antes de usar, converta e valide intervalo.

17. ConfigModule

npm install @nestjs/config
@Module({
  imports: [
    ConfigModule.forRoot({
      isGlobal: true,
      validate: validateEnvironment,
    }),
  ],
})
export class AppModule {}

O pacote usa dotenv internamente e disponibiliza ConfigService.

18. Validando configuração

export function validateEnvironment(raw: Record<string, unknown>): AppEnvironment {
  const nodeEnv = raw.NODE_ENV ?? 'development';
  if (!['development', 'test', 'production'].includes(String(nodeEnv))) {
    throw new Error('NODE_ENV inválido.');
  }

  const port = Number(raw.PORT ?? 3000);
  if (!Number.isInteger(port) || port < 1 || port > 65535) {
    throw new Error('PORT deve estar entre 1 e 65535.');
  }

  if (!raw.DATABASE_PASSWORD) {
    throw new Error('DATABASE_PASSWORD é obrigatório.');
  }

  return { nodeEnv, port, databasePassword: String(raw.DATABASE_PASSWORD) };
}

Se a função lança, a inicialização falha antes de abrir a porta.

19. Falhar cedo

configuração inválida
→ inicialização interrompida
→ processo não anuncia prontidão
→ operador corrige o ambiente
→ nova inicialização

Isso é melhor que iniciar e falhar apenas na primeira requisição importante.

20. Configuração pública versus segredo

Pode aparecer em documentação Deve ser protegido
nome da aplicação senha do MySQL
porta padrão token de API
ambientes aceitos chave privada
limite de paginação segredo JWT

O nome de uma variável pode ser documentado. O valor secreto não.

21. .env e .env.example

.env local:

DATABASE_PASSWORD=valor-real-local

.env.example versionável:

DATABASE_PASSWORD=troque-este-valor

Inclua .env no .gitignore. Se um segredo for publicado, removê-lo do arquivo não é suficiente: revogue e substitua o segredo.

22. ConfigService

@Injectable()
export class DatabaseOptionsFactory {
  constructor(private readonly config: ConfigService) {}

  create() {
    return {
      host: this.config.getOrThrow<string>('DATABASE_HOST'),
      port: this.config.getOrThrow<number>('DATABASE_PORT'),
    };
  }
}

Depois da validação, consumidores recebem valores previsíveis. Evite espalhar leituras de process.env por toda a aplicação.

23. Configuração por namespace

Agrupar valores reduz colisões:

export default registerAs('database', () => ({
  host: process.env.DATABASE_HOST,
  port: Number(process.env.DATABASE_PORT ?? 3306),
}));

No Módulo 6, a configuração de MySQL e Prisma será aprofundada.

24. Não registrar segredos

Errado:

logger.log(JSON.stringify(process.env));

Correto:

logger.log({
  environment,
  port,
  databaseHost,
  databasePasswordConfigured: Boolean(databasePassword),
});

Registre presença ou versão, não o valor secreto.

25. Configuração no front-end

Tudo entregue ao navegador pode ser inspecionado. Nunca coloque senha de MySQL ou chave privada em arquivos Angular, HTML ou JavaScript público.

segredo → somente servidor
valor público → pode chegar ao navegador

26. XAMPP e os processos

Apache/XAMPP → serve o curso e a interface
NestJS        → lê configuração da API e escuta sua própria porta
MySQL         → lê sua própria configuração de servidor

Uma configuração não substitui a outra. Evite conflito com a porta usada pelo Apache.

27. Desenvolvimento versus produção

Em desenvolvimento, registros internos podem ser mais detalhados. A resposta pública deve continuar segura. Em produção:

  • não habilite stack trace para o cliente;
  • exija segredos reais;
  • valide origem e conexões;
  • use registros estruturados;
  • não use valores padrão inseguros.

28. Roteiro de implementação

Etapa Tempo sugerido
Classificação de erros 15 min
Exceções e contrato 20 min
Filter global e correlação 25 min
Configuração e ambientes 20 min
Validação da inicialização 20 min
Laboratório 15 min
Revisão 5 min

29. Laboratório guiado

Abra o Centro de erros e configuração.

Etapa 1 — Inicialização válido

  1. escolha development, porta 3000 e senha configurada;
  2. execute a inicialização;
  3. observe que o segredo aparece apenas como “configurado”.

Etapa 2 — Configuração inválida

  1. use porta 70000;
  2. remova a senha;
  3. compare os erros de inicialização;
  4. confirme que nenhuma requisição pode ser enviada.

Etapa 3 — Erros esperados

  1. restaure a configuração;
  2. simule validação, recurso ausente e conflito;
  3. compare status, code e mensagem.

Etapa 4 — Erro inesperado

  1. selecione falha interna;
  2. envie a requisição;
  3. compare resposta pública e registro interno;
  4. confirme que stack e segredo não aparecem na resposta.

Etapa 5 — Correlação

Copie o correlationId da resposta e encontre o mesmo valor no registro interno.

30. Erros comuns

Responder 200 com { success: false }

Esconde a semântica HTTP e dificulta clientes e monitoramento.

Devolver error.message de qualquer exceção

Pode vazar SQL, caminhos e segredos.

Capturar e ignorar

try { ... } catch { return undefined; }

Remove contexto e cria falhas silenciosas.

Iniciar com configuração inválida

Transfere o problema para uma requisição futura.

Versionar .env

Publica segredos no histórico do repositório.

Colocar segredo no Angular

Qualquer usuário consegue inspecioná-lo.

31. Exercício de fixação

Implemente:

  • DocumentNotFoundException com código estável;
  • conflito de nome duplicado;
  • filter que inclui path, timestamp e correlação;
  • configuração MAX_UPLOAD_MB validada entre 1 e 20;
  • .env.example sem valores reais.

32. Desafio individual

Adicione um cenário de MySQL indisponível:

  1. traduza a falha técnica para 503;
  2. não exponha host, porta ou consulta;
  3. registre detalhes internamente;
  4. inclua correlation ID;
  5. diferencie falha transitória de entrada inválida.

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

  • Classifico erros esperados e inesperados.
  • Uso status HTTP coerentes.
  • Minha API possui formato estável de erro.
  • O filtro global não expõe stack trace.
  • Resposta e registro compartilham correlação.
  • Valido ambiente e porta na inicialização.
  • Segredos não aparecem em registros ou respostas.
  • .env não é versionado.
  • Angular não recebe segredo do servidor.
  • Configuração inválida impede a API de iniciar.

34. Rubrica da entrega

Critério Pontos
Classificação e status 15
Contrato de erro 20
Filtro global 20
Correlação e registros seguros 15
ConfigModule 10
Validação do ambiente 15
Proteção de segredos 5
Total 100

35. Fontes oficiais

Próxima aula

Na Aula 5.6, documentaremos os endpoints, DTOs, exemplos, status e autenticação com OpenAPI, criando um contrato navegável para clientes e testes.