# Aula 5.5 — Erros, filtros e configuração

## 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`](../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:

```ts
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.

```ts
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:

```json
{
  "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

```ts
@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`

```ts
@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.

```ts
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:

```json
{
  "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:

```text
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

```text
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:

```text
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

```ts
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`

```bash
npm install @nestjs/config
```

```ts
@Module({
  imports: [
    ConfigModule.forRoot({
      isGlobal: true,
      validate: validateEnvironment,
    }),
  ],
})
export class AppModule {}
```

O pacote usa dotenv internamente e disponibiliza `ConfigService`.

## 18. Validando configuração

```ts
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

```text
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:

```text
DATABASE_PASSWORD=valor-real-local
```

`.env.example` versionável:

```text
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`

```ts
@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:

```ts
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:

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

Correto:

```ts
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.

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

## 26. XAMPP e os processos

```text
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](../exemplos/aula-5.5/index.html).

### 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

```ts
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

- [NestJS — Exception filters](https://docs.nestjs.com/exception-filters)
- [NestJS — Configuration](https://docs.nestjs.com/techniques/configuration)
- [NestJS — ciclo de vida da requisição](https://docs.nestjs.com/faq/request-lifecycle)
- [NestJS — Execution context](https://docs.nestjs.com/fundamentals/execution-context)

## 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.
