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?
.envpode 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:
- diferenciar erro esperado, falha externa e defeito de programação;
- usar exceções HTTP com status coerentes;
- criar um formato estável de erro;
- implementar um filtro global de exceções;
- correlacionar resposta e registro interno;
- evitar exposição de stack trace e segredos;
- carregar configuração com
ConfigModule; - validar ambiente, porta e valores obrigatórios na inicialização;
- separar configuração pública de segredo;
- explicar diferenças entre middleware, interceptor, pipe e filter.
Pré-requisitos
- aulas 5.1 a 5.4 concluídas;
- noções de status HTTP;
ValidationPipeglobal;- 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
- escolha
development, porta3000e senha configurada; - execute a inicialização;
- observe que o segredo aparece apenas como “configurado”.
Etapa 2 — Configuração inválida
- use porta
70000; - remova a senha;
- compare os erros de inicialização;
- confirme que nenhuma requisição pode ser enviada.
Etapa 3 — Erros esperados
- restaure a configuração;
- simule validação, recurso ausente e conflito;
- compare status,
codee mensagem.
Etapa 4 — Erro inesperado
- selecione falha interna;
- envie a requisição;
- compare resposta pública e registro interno;
- 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:
DocumentNotFoundExceptioncom código estável;- conflito de nome duplicado;
- filter que inclui
path, timestamp e correlação; - configuração
MAX_UPLOAD_MBvalidada entre 1 e 20; .env.examplesem valores reais.
32. Desafio individual
Adicione um cenário de MySQL indisponível:
- traduza a falha técnica para
503; - não exponha host, porta ou consulta;
- registre detalhes internamente;
- inclua correlation ID;
- 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.
-
.envnã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
- NestJS — Configuration
- NestJS — ciclo de vida da requisição
- NestJS — 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.