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

CheckO que detectaComo testar
Banco de dadosPool esgotado, problemas de rede, lag de replicacaoSELECT 1 com timeout
Cache (Redis)Conexoes falhando, memoria altaPING com timeout
APIs externasQuedas de terceiros (Stripe, Auth0)HEAD request ou endpoint leve
MemoriaMemory leaks, risco de OOMVerificar 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 OK por 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.