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?):

    typescript
    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
    # 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=healthy

    Health check com Fastify

    Implementação para APIs Fastify com plugin dedicado:

    typescript
    // 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' } → 503

    Integrar com Uptime Kuma e alertas

    Monitorar /health externamente com Uptime Kuma self-hosted:

    bash
    # 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 Notification

    Resposta estruturada para load balancers

    Formato padronizado compatível com AWS, GCP e Kubernetes:

    typescript
    // 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)
    })
    Dica
    Retornar 503 (não 200) quando a aplicação está unhealthy — load balancers e proxies usam o status HTTP para decidir o roteamento, não o conteúdo JSON.

    $ 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