Sua API retorna 200 OK em cada request. O banco de dados esta conectado. Redis esta rodando. Tudo funciona.
Ai o connection pool do PostgreSQL enche. Requests comecam a enfileirar. Tempos de resposta sobem de 50ms para 8 segundos. Seu app esta tecnicamente "no ar" mas praticamente inutilizavel. O load balancer continua mandando trafego porque nao sabe que algo esta errado.
Um health check endpoint teria detectado isso em segundos.
O que e um health check endpoint
Um health check e uma rota dedicada — geralmente /health ou /api/health — que reporta se sua aplicacao pode fazer seu trabalho. Nao apenas "o processo esta rodando", mas "este servidor consegue lidar com requests reais agora?"
Tres coisas consomem health checks:
- Load balancers (AWS ALB, Nginx) — redirecionam trafego de instancias nao saudaveis
- Orquestradores de containers (Kubernetes, Docker) — reiniciam containers que falham
- Ferramentas de monitoramento (Nurbak Watch, UptimeRobot) — alertam quando algo quebra
O que um bom health check deve incluir
| Check | O que detecta | Como testar |
|---|---|---|
| Banco de dados | Pool esgotado, problemas de rede, lag de replicacao | SELECT 1 com timeout |
| Cache (Redis) | Conexoes falhando, memoria alta | PING com timeout |
| APIs externas | Quedas de terceiros (Stripe, Auth0) | HEAD request ou endpoint leve |
| Memoria | Memory leaks, risco de OOM | Verificar process.memoryUsage() |
Implementacao em Next.js App Router
Crie app/api/health/route.ts:
// app/api/health/route.ts
import { NextResponse } from 'next/server'
async function checkWithTimeout(name, fn, timeoutMs = 3000) {
const start = Date.now()
try {
await Promise.race([
fn(),
new Promise((_, reject) =>
setTimeout(() => reject(new Error('Timeout')), timeoutMs)
),
])
return { name, status: 'healthy', latency: Date.now() - start }
} catch (error) {
return {
name, status: 'unhealthy',
latency: Date.now() - start,
message: error.message,
}
}
}
export async function GET() {
const checks = await Promise.all([
checkWithTimeout('database', async () => {
const { Pool } = await import('pg')
const pool = new Pool({ connectionString: process.env.DATABASE_URL })
const client = await pool.connect()
try { await client.query('SELECT 1') }
finally { client.release(); await pool.end() }
}),
checkWithTimeout('memory', async () => {
const usage = process.memoryUsage()
const pct = (usage.heapUsed / usage.heapTotal) * 100
if (pct > 90) throw new Error(`Heap at ${pct.toFixed(1)}%`)
}),
])
const isHealthy = checks.every(c => c.status === 'healthy')
return NextResponse.json(
{ status: isHealthy ? 'healthy' : 'unhealthy', checks },
{
status: isHealthy ? 200 : 503,
headers: { 'Cache-Control': 'no-cache, no-store, must-revalidate' },
}
)
}Implementacao em Express
// routes/health.js
router.get('/health', async (req, res) => {
const checks = await Promise.all([
checkWithTimeout('database', async () => {
await req.app.get('dbPool').query('SELECT 1')
}),
checkWithTimeout('redis', async () => {
await req.app.get('redisClient').ping()
}),
checkWithTimeout('memory', async () => {
const pct = (process.memoryUsage().heapUsed /
process.memoryUsage().heapTotal) * 100
if (pct > 90) throw new Error(`Heap at ${pct.toFixed(1)}%`)
}),
])
const isHealthy = checks.every(c => c.status === 'healthy')
res.status(isHealthy ? 200 : 503)
.set('Cache-Control', 'no-cache, no-store, must-revalidate')
.json({ status: isHealthy ? 'healthy' : 'unhealthy', checks })
})Implementacao em FastAPI
# routes/health.py
import asyncio, time, psutil
from fastapi import APIRouter
from fastapi.responses import JSONResponse
router = APIRouter()
async def check_with_timeout(name, coro, timeout_s=3.0):
start = time.monotonic()
try:
await asyncio.wait_for(coro(), timeout=timeout_s)
return {"name": name, "status": "healthy",
"latency": round((time.monotonic() - start) * 1000)}
except Exception as e:
return {"name": name, "status": "unhealthy",
"latency": round((time.monotonic() - start) * 1000),
"message": str(e)}
@router.get("/health")
async def health_check():
checks = await asyncio.gather(
check_with_timeout("database", check_database),
check_with_timeout("redis", check_redis),
check_with_timeout("memory", check_memory),
)
is_healthy = all(c["status"] == "healthy" for c in checks)
return JSONResponse(
content={"status": "healthy" if is_healthy else "unhealthy", "checks": checks},
status_code=200 if is_healthy else 503,
headers={"Cache-Control": "no-cache, no-store, must-revalidate"},
)Erros comuns a evitar
- Sem timeout nos checks: Se seu DB esta lento, o health check trava por 30+ segundos. O load balancer marca todas as instancias como nao saudaveis.
- Cachear a resposta: Um CDN cacheando
200 OKpor 60 segundos significa trafego enviado para uma instancia morta durante um minuto inteiro. - Verificar demais: Nao cheque servicos opcionais. Mixpanel fora do ar nao deveria tornar seu app "nao saudavel."
- Expor informacoes sensiveis: Nao inclua connection strings nem IPs internos na resposta.
Alem do health check: monitorar cada API route
Um health check testa um request sintetico a cada 30-60 segundos. E uma boa base, mas nao detecta erros intermitentes, spikes de latencia nem falhas especificas por endpoint.
Nurbak Watch monitora cada API route de dentro do seu servidor:
// instrumentation.ts
import { initWatch } from '@nurbak/watch'
export function register() {
initWatch({
apiKey: process.env.NURBAK_WATCH_KEY,
})
}5 linhas e voce tem: cada rota monitorada, latencia P95 real, taxas de erro e alertas por Slack, email ou WhatsApp em menos de 10 segundos.
O health check e seu detector de fumaca. Nurbak Watch e o sensor em cada sala.
Comece
Nurbak Watch esta em beta e e gratis durante o lancamento. Monitora cada API route (nao so /health) de dentro do seu servidor.

