Health check /health em APIs Node.js
Um endpoint /health bem implementado é a diferença entre descobrir que sua API está com problemas antes ou depois do usuário reclamar. Load balancers, Docker, Kubernetes, Uptime Kuma e Railway usam esse endpoint para decidir se devem rotear tráfego ou reiniciar o container.
Endpoint /health simples e com profundidade
Dois níveis de health check: liveness (está vivo?) e readiness (pode receber tráfego?):
import express from 'express'
import { Pool } from 'pg'
import { createClient } from 'redis'
const app = express()
const db = new Pool({ connectionString: process.env.DATABASE_URL })
const redis = createClient({ url: process.env.REDIS_URL })
// Liveness: apenas verifica se o processo está respondendo:
app.get('/health/live', (req, res) => {
res.json({ status: 'ok', timestamp: new Date().toISOString() })
})
// Readiness: verifica dependências críticas:
app.get('/health/ready', async (req, res) => {
const checks: Record<string, { status: string; latency?: number; error?: string }> = {}
let overallOk = true
// Verificar PostgreSQL:
try {
const start = Date.now()
await db.query('SELECT 1')
checks.database = { status: 'ok', latency: Date.now() - start }
} catch (err) {
checks.database = { status: 'error', error: (err as Error).message }
overallOk = false
}
// Verificar Redis:
try {
const start = Date.now()
await redis.ping()
checks.redis = { status: 'ok', latency: Date.now() - start }
} catch (err) {
checks.redis = { status: 'error', error: (err as Error).message }
overallOk = false
}
// Verificar uso de memória:
const mem = process.memoryUsage()
const heapUsedMB = Math.round(mem.heapUsed / 1024 / 1024)
checks.memory = {
status: heapUsedMB < 400 ? 'ok' : 'warning',
latency: heapUsedMB,
}
res.status(overallOk ? 200 : 503).json({
status: overallOk ? 'ok' : 'degraded',
version: process.env.npm_package_version,
uptime: Math.round(process.uptime()),
checks,
})
})
// Alias mais comum:
app.get('/health', (req, res) => res.redirect('/health/ready'))Integrar com Docker healthcheck
Docker reinicia o container automaticamente quando o healthcheck falha:
# Dockerfile — adicionar healthcheck:
FROM node:22-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY dist/ ./dist/
# Healthcheck nativo do Docker:
HEALTHCHECK --interval=30s --timeout=5s --start-period=15s --retries=3 \
CMD wget -qO- http://localhost:3000/health/live || exit 1
# (ou com curl se estiver disponível):
# HEALTHCHECK CMD curl -f http://localhost:3000/health/live || exit 1
USER node
CMD ["node", "dist/index.js"]
# docker-compose.yml:
services:
api:
build: .
healthcheck:
test: ["CMD", "wget", "-qO-", "http://localhost:3000/health/live"]
interval: 30s
timeout: 5s
retries: 3
start_period: 20s
# Traefik ou Nginx só roteia para containers healthy:
# status healthy → container recebe tráfego
# Ver status do healthcheck:
docker inspect --format='{{.State.Health.Status}}' nome-do-container
docker ps --filter health=healthyHealth check com Fastify
Implementação para APIs Fastify com plugin dedicado:
// npm install fastify @fastify/under-pressure
import Fastify from 'fastify'
import underPressure from '@fastify/under-pressure'
const app = Fastify({ logger: true })
// Plugin under-pressure: monitora event loop lag:
await app.register(underPressure, {
maxEventLoopDelay: 1000, // ms — degradado se event loop atrasar > 1s
maxHeapUsedBytes: 500_000_000, // 500 MB
maxRssBytes: 750_000_000,
exposeStatusRoute: {
routeOpts: { url: '/health' },
routeSchemaOpts: { tags: ['health'] },
},
healthCheck: async (fastifyInstance) => {
// Verificações customizadas — retornar false = unhealthy:
try {
await fastifyInstance.db.query('SELECT 1')
return true
} catch (err) {
return false
}
},
healthCheckInterval: 15_000, // verificar a cada 15s
})
// Resposta do under-pressure:
// { status: 'ok' } → 200
// { status: 'Service Unavailable' } → 503Integrar com Uptime Kuma e alertas
Monitorar /health externamente com Uptime Kuma self-hosted:
# No Uptime Kuma — adicionar monitor:
# Type: HTTP(s)
# URL: https://api.seudominio.com.br/health
# Interval: 60 seconds
# Keyword: "ok" ← deve aparecer no JSON de resposta
# Expected Status Code: 200
# Configuração JSON esperada pelo Uptime Kuma:
# { "status": "ok", ... } → UP
# { "status": "degraded" } ou 503 → DOWN
# Para monitorar o banco separadamente:
# Type: HTTP(s)
# URL: https://api.seudominio.com.br/health/ready
# Expected Status Code: 200
# Status page pública — mostrar status dos serviços:
# Settings → Status Pages → New Status Page
# Adicionar os monitors da API, banco e Redis
# URL: status.seudominio.com.br
# Notificações: Telegram, Slack, email, webhook
# Settings → Notifications → Add NotificationResposta estruturada para load balancers
Formato padronizado compatível com AWS, GCP e Kubernetes:
// Formato compatível com o padrão de health checks da maioria dos serviços:
interface HealthResponse {
status: 'ok' | 'degraded' | 'error'
version: string
environment: string
uptime: number
timestamp: string
checks: {
[key: string]: {
status: 'ok' | 'warning' | 'error'
latencyMs?: number
message?: string
}
}
}
app.get('/health', async (req, res) => {
const response: HealthResponse = {
status: 'ok',
version: process.env.npm_package_version ?? 'unknown',
environment: process.env.NODE_ENV ?? 'production',
uptime: Math.round(process.uptime()),
timestamp: new Date().toISOString(),
checks: {},
}
// Não expor detalhes de erro em ambientes públicos:
const isInternal = req.ip === '127.0.0.1' || req.headers['x-internal'] === process.env.INTERNAL_SECRET
// ... executar verificações ...
if (!isInternal) {
// Simplificar resposta para requests externos:
for (const key in response.checks) {
delete response.checks[key].message
}
}
res.status(response.status === 'ok' ? 200 : 503).json(response)
})$ runstack deploy --plan starter
Não quer configurar manualmente?
Não quer configurar manualmente? Implante o VPS para APIs em menos de 3 minutos com a Runstack. Infraestrutura da OPEN DATACENTER, com servidores no Brasil.
Perguntas frequentes
Conteúdos relacionados