Load balancers, orquestradores e sistemas de monitoramento precisam saber se uma aplicação está viva e se pode receber tráfego. Os Health Checks no Node.js expõem endpoints simples que representam o estado do processo e de suas dependências.
Um único endpoint que consulta banco, cache, fila e serviços externos pode causar reinícios desnecessários durante uma falha temporária. Por outro lado, responder sempre 200 mantém uma instância quebrada no tráfego. A solução é separar liveness, readiness e startup, com limites claros e respostas rápidas.
Neste guia, você aprenderá a criar probes, verificar dependências, aplicar timeout, evitar efeito cascata, integrar graceful shutdown e definir informações seguras para observabilidade.
Liveness, readiness e startup
- Liveness: indica se o processo está funcionando e não ficou irrecuperavelmente travado.
- Readiness: indica se a instância está pronta para receber tráfego.
- Startup: protege aplicações que precisam de mais tempo para inicializar.
Esses sinais possuem finalidades diferentes. Uma indisponibilidade do banco pode tornar a instância não pronta, mas não exige necessariamente reiniciar o processo.
A documentação do Kubernetes sobre liveness, readiness e startup probes apresenta o modelo. Para o servidor HTTP nativo, consulte a documentação do Node.js.
Veja também como criar uma API com Node.js e o que é Node.js.
Endpoint de liveness
app.get('/health/live', (req, res) => {
res.status(200).json({
status: 'alive'
});
});Liveness deve ser barata e não depender de rede externa. Se o endpoint consulta o banco, uma falha do banco pode fazer o orquestrador reiniciar todas as instâncias sem resolver a causa.
Estado de readiness
let ready = false;
async function start() {
await database.connect();
await cache.connect();
ready = true;
}
app.get('/health/ready', (req, res) => {
res.status(ready ? 200 : 503).json({
status: ready ? 'ready' : 'not_ready'
});
});A flag representa inicialização e encerramento. Antes de fechar recursos durante deploy, mude para false. O guia de Graceful Shutdown no Node.js explica essa sequência.
Verificando banco de dados
async function checkDatabase() {
const startedAt = performance.now();
await database.query('SELECT 1');
return {
status: 'up',
durationMs: performance.now() - startedAt
};
}Use uma consulta mínima e limite o tempo. Não execute migrações, relatórios ou leituras pesadas em cada probe.
Timeout por dependência
async function withTimeout(operation, milliseconds) {
return Promise.race([
operation(),
new Promise((resolve, reject) => {
setTimeout(() => {
reject(new Error('health check timeout'));
}, milliseconds);
})
]);
}Uma implementação melhor deve cancelar a operação subjacente com AbortSignal quando suportado. Veja AbortController no Node.js.
Readiness detalhada
app.get('/health/ready', async (req, res) => {
const checks = await Promise.allSettled([
withTimeout(checkDatabase, 1000),
withTimeout(checkCache, 500)
]);
const healthy = checks.every(item => item.status === 'fulfilled');
res.status(healthy ? 200 : 503).json({
status: healthy ? 'ready' : 'not_ready',
checks: {
database: checks[0].status,
cache: checks[1].status
}
});
});Não inclua URLs, credenciais, stack traces ou detalhes internos na resposta pública.
Dependências obrigatórias e opcionais
Nem toda dependência precisa retirar a instância do tráfego. Se cache está indisponível e a aplicação consegue usar o banco, readiness pode continuar positiva enquanto uma métrica e alerta indicam degradação.
const required = [databaseCheck];
const optional = [cacheCheck, analyticsCheck];Documente qual recurso é essencial para cada função da aplicação.
Cacheando o resultado
Probes frequentes em várias instâncias podem sobrecarregar o banco. Execute verificações em intervalo controlado e sirva o último resultado:
let healthState = {
ready: false,
checkedAt: null
};
setInterval(async () => {
healthState = await calculateHealth();
}, 5000).unref();Considere a idade máxima. Um resultado antigo não deve permanecer saudável indefinidamente.
Startup probe
Aplicações que carregam modelos, aquecem cache ou executam migrações podem demorar. Uma startup probe evita que liveness reinicie o processo antes da conclusão. O endpoint pode usar a mesma flag de inicialização com política diferente na infraestrutura.
Event loop e liveness
Um endpoint não responde quando o event loop está completamente bloqueado, e o timeout da probe detecta isso. Para monitoramento interno, meça atraso do event loop e memória, mas não reinicie apenas por uma única oscilação.
const { monitorEventLoopDelay } = require('node:perf_hooks');
const histogram = monitorEventLoopDelay();
histogram.enable();Tarefas de CPU devem ser tratadas com Worker Threads no Node.js.
Memória e disco
Memória alta não significa automaticamente que o processo está morto. Crie alertas e políticas graduais. Para serviços que gravam arquivos, espaço em disco pode ser um requisito de readiness, mas a consulta precisa ser barata.
Protegendo endpoints
Endpoints usados internamente podem ficar em porta administrativa ou rede restrita. Se forem públicos, retorne apenas estado geral. Não revele nomes de hosts, versões exatas, topologia ou mensagens de erro.
HTTP status
Use 200 quando saudável e 503 quando temporariamente indisponível para tráfego. A resposta deve ser pequena e possuir Cache-Control: no-store quando intermediários não devem armazená-la.
res.setHeader('cache-control', 'no-store');Evite autenticação complexa na probe
Uma autenticação que depende do mesmo serviço externo pode impedir a infraestrutura de verificar a aplicação. Restrinja por rede, porta ou política do cluster. Se usar token, mantenha a validação local e simples.
Health checks e deploy
Durante deploy:
- a nova instância inicia não pronta;
- conecta dependências e aquece recursos;
- readiness passa a 200;
- a instância antiga muda readiness para 503;
- o tráfego é drenado;
- recursos são fechados.
Essa ordem evita enviar tráfego cedo demais ou interromper requisições existentes.
Observabilidade
Registre mudanças de estado, não cada chamada da probe. Crie métricas para duração, falhas por dependência, idade do último check e quantidade de transições. Combine com OpenTelemetry no Node.js.
Testando
Teste estado inicial, dependência saudável, timeout, falha opcional, shutdown e resultado antigo. Em integração, simule o banco indisponível e confirme que liveness continua viva enquanto readiness muda para 503.
Erros comuns
- Usar a mesma lógica para liveness e readiness.
- Consultar todas as dependências em cada liveness.
- Não definir timeout.
- Sobrecarregar o banco com probes.
- Retornar detalhes sensíveis.
- Continuar pronto durante shutdown.
- Considerar cache opcional como falha total.
- Armazenar resposta saudável por tempo indefinido.
Boas práticas
- Mantenha liveness local e barata.
- Use readiness para dependências essenciais.
- Defina timeout curto por check.
- Classifique dependências obrigatórias e opcionais.
- Cacheie verificações com idade máxima.
- Retorne respostas pequenas e seguras.
- Mude readiness antes do shutdown.
- Monitore transições de estado.
- Teste falhas reais.
- Ajuste intervalos à capacidade da infraestrutura.
Conclusão
Os Health Checks no Node.js permitem que a infraestrutura diferencie processo vivo de instância pronta. A separação entre liveness, readiness e startup evita reinícios em cascata e tráfego para serviços ainda indisponíveis.
Probes precisam ser rápidas, limitadas e seguras. Com dependências classificadas, timeouts e integração ao graceful shutdown, health checks se tornam sinais confiáveis da operação da aplicação.




