Grafana: dashboards para Node.js e PostgreSQL

Grafana transforma métricas do Prometheus em dashboards visuais que revelam o comportamento da sua aplicação em tempo real. Um dashboard bem construído responde três perguntas de imediato: o sistema está saudável? onde está o gargalo? o que mudou agora? Este guia cobre a criação de painéis práticos para APIs Node.js em produção.

Instalar Grafana com Docker Compose

Stack Prometheus + Grafana com configuração mínima:

yaml
# docker-compose.yml:
services:
  prometheus:
    image: prom/prometheus:latest
    container_name: prometheus
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml
      - prometheus-data:/prometheus
    command:
      - '--config.file=/etc/prometheus/prometheus.yml'
      - '--storage.tsdb.retention.time=30d'
    ports:
      - "127.0.0.1:9090:9090"
    restart: unless-stopped

  grafana:
    image: grafana/grafana-oss:latest
    container_name: grafana
    environment:
      GF_SECURITY_ADMIN_PASSWORD: "SUA_SENHA_FORTE"
      GF_USERS_ALLOW_SIGN_UP: "false"
      GF_SERVER_ROOT_URL: "https://grafana.seudominio.com.br"
      GF_SMTP_ENABLED: "true"
      GF_SMTP_HOST: "smtp.seudominio.com.br:587"
    volumes:
      - grafana-data:/var/lib/grafana
      - ./grafana/provisioning:/etc/grafana/provisioning
    ports:
      - "127.0.0.1:3001:3000"
    restart: unless-stopped
    depends_on:
      - prometheus

volumes:
  prometheus-data:
  grafana-data:

# prometheus.yml — scrape da API Node.js:
# scrape_configs:
#   - job_name: 'nodejs-api'
#     static_configs:
#       - targets: ['host.docker.internal:3000']
#     metrics_path: '/metrics'

Dashboard de API Node.js: latência e throughput

Queries PromQL essenciais para monitorar uma API:

bash
# Queries PromQL para painéis Grafana:

# 1. Throughput (req/s) — Rate de requisições por endpoint:
rate(http_request_duration_seconds_count{job="nodejs-api"}[5m])

# Por endpoint (top 10 mais chamados):
topk(10, sum by (route, method) (
  rate(http_request_duration_seconds_count[5m])
))

# 2. Latência P50/P95/P99 — Percentis de tempo de resposta:
histogram_quantile(0.50, sum by (le, route) (
  rate(http_request_duration_seconds_bucket{job="nodejs-api"}[5m])
))
histogram_quantile(0.95, sum by (le, route) (
  rate(http_request_duration_seconds_bucket{job="nodejs-api"}[5m])
))
histogram_quantile(0.99, sum by (le) (
  rate(http_request_duration_seconds_bucket{job="nodejs-api"}[5m])
))

# 3. Taxa de erro (% de 4xx e 5xx):
sum(rate(http_request_duration_seconds_count{status_code=~"[45].."}[5m]))
/
sum(rate(http_request_duration_seconds_count[5m])) * 100

# 4. Erros por endpoint (para identificar rotas problemáticas):
sum by (route, status_code) (
  rate(http_request_duration_seconds_count{status_code=~"5.."}[5m])
)

# 5. Conexões ativas Node.js:
nodejs_active_handles_total{job="nodejs-api"}

Painel de uso de recursos do sistema

Métricas do Node Exporter para CPU, memória e disco:

bash
# Queries PromQL para recursos do servidor:

# CPU usage % (por core e total):
100 - (avg by (instance) (
  rate(node_cpu_seconds_total{mode="idle"}[5m])
) * 100)

# Por modo (user/system/iowait):
sum by (mode) (
  rate(node_cpu_seconds_total{instance="servidor:9100"}[5m])
) * 100

# Memória livre %:
(node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes) * 100

# Memória por componente (painel tipo "stack"):
node_memory_MemTotal_bytes - node_memory_MemFree_bytes
  - node_memory_Buffers_bytes - node_memory_Cached_bytes

# Disco: uso % por filesystem:
(
  node_filesystem_size_bytes{mountpoint="/"}
  - node_filesystem_free_bytes{mountpoint="/"}
) / node_filesystem_size_bytes{mountpoint="/"} * 100

# I/O de disco (bytes/s):
rate(node_disk_read_bytes_total{device="sda"}[5m])
rate(node_disk_written_bytes_total{device="sda"}[5m])

# Rede (bytes/s por interface):
rate(node_network_receive_bytes_total{device="eth0"}[5m])
rate(node_network_transmit_bytes_total{device="eth0"}[5m])

Painel PostgreSQL com postgres_exporter

Monitorar banco de dados PostgreSQL no Grafana:

yaml
# Instalar postgres_exporter:
# docker-compose.yml (adicionar ao stack):
  postgres-exporter:
    image: prometheuscommunity/postgres-exporter:latest
    environment:
      DATA_SOURCE_NAME: "postgresql://monitoring:senha@postgres:5432/mydb?sslmode=disable"
    ports:
      - "127.0.0.1:9187:9187"

# Queries PromQL para PostgreSQL:

# Conexões ativas vs máximo:
pg_stat_activity_count{datname="mydb", state="active"}
pg_settings_max_connections

# Transações por segundo:
rate(pg_stat_database_xact_commit{datname="mydb"}[5m])
rate(pg_stat_database_xact_rollback{datname="mydb"}[5m])

# Cache hit ratio (deve ficar acima de 99%):
pg_stat_database_blks_hit{datname="mydb"}
/ (
  pg_stat_database_blks_hit{datname="mydb"}
  + pg_stat_database_blks_read{datname="mydb"}
) * 100

# Queries lentas (via pg_stat_statements):
pg_stat_statements_mean_time_seconds
# Ordenar por: topk(10, pg_stat_statements_mean_time_seconds)

# Tamanho do banco:
pg_database_size_bytes{datname="mydb"}

# Locks esperando:
pg_locks_count{mode="ExclusiveLock", granted="false"}

Provisioning automático de dashboards

Versionar dashboards como código e carregar automaticamente:

yaml
# Estrutura de arquivos:
# grafana/
#   provisioning/
#     datasources/
#       prometheus.yml   → configurar Prometheus como datasource
#     dashboards/
#       dashboard.yml    → apontar para pasta de JSON
#   dashboards/
#     nodejs-api.json    → dashboard exportado

# grafana/provisioning/datasources/prometheus.yml:
apiVersion: 1
datasources:
  - name: Prometheus
    type: prometheus
    access: proxy
    url: http://prometheus:9090
    isDefault: true
    editable: false

# grafana/provisioning/dashboards/dashboard.yml:
apiVersion: 1
providers:
  - name: 'default'
    orgId: 1
    folder: ''
    type: file
    disableDeletion: false
    updateIntervalSeconds: 30
    options:
      path: /var/lib/grafana/dashboards

# Exportar dashboard existente como JSON:
# Grafana UI → Dashboard → Share → Export → Save to file

# Importar dashboard da comunidade (Grafana.com/dashboards):
# Dashboard ID 1860 → Node Exporter Full (métricas de sistema)
# Dashboard ID 11159 → Node.js Application Dashboard
# Dashboard ID 9628 → PostgreSQL Database

# API Grafana para importar via curl:
curl -X POST http://admin:senha@localhost:3001/api/dashboards/import   -H 'Content-Type: application/json'   -d '{"dashboard": {...}, "overwrite": true, "folderId": 0}'

Não quer configurar manualmente?

Implante o VPS para Node.js em menos de 5 minutos com a Runstack. Infraestrutura da OPEN Datacenter, com servidores em São Paulo.

Perguntas frequentes

Grafana Cloud (SaaS): plano gratuito generoso (10k series, 50GB logs, 14 dias retenção), zero manutenção de infraestrutura, inclui Loki e Tempo integrados. Self-hosted: controle total dos dados, sem limite de série temporal, retenção configurável, custo zero além do servidor. Para equipes pequenas ou MVP: Grafana Cloud poupa tempo. Para dados sensíveis ou alto volume: self-hosted. A stack Docker Compose com Prometheus + Grafana + Loki em um VPS de 2GB RAM cobre a maioria dos projetos.

Prometheus é eficiente: até ~1 milhão de series ativas por instância em hardware modesto (8GB RAM). Cada série é uma combinação de nome + labels. Para APIs Node.js com poucas rotas: centenas de series. Para sistemas com muitos labels (por usuário, por tenant): pode crescer rapidamente. Cuidado com cardinality alta: labels como user_id ou request_id criam uma série por valor, use labels de baixa cardinalidade (método HTTP, código de status, rota normalizada).

grafana.com/grafana/dashboards tem milhares de dashboards. Os mais usados para Node.js/VPS: ID 1860 (Node Exporter Full, métricas de sistema), ID 11159 (Node.js Application), ID 9628 (PostgreSQL). Importar: Grafana → Dashboards → Import → inserir ID → selecionar datasource Prometheus. Funciona imediatamente se o exporter correspondente estiver configurado com os labels corretos.

Use folders para agrupar dashboards por serviço/equipe (ex: pasta "API", pasta "Banco de Dados", pasta "Infraestrutura"). Use variables no dashboard para selecionar instância/ambiente dinamicamente: $instance, $job, assim um mesmo dashboard cobre produção e staging. Tags ajudam na busca. Para equipes maiores: crie organizations separadas no Grafana com controles de acesso por time.

rate(): calcula a taxa média de crescimento no intervalo especificado (ex: [5m]), suaviza picos, bom para gráficos de tendência. irate(): calcula a taxa instantânea baseada nos dois últimos pontos, mais sensível a picos, bom para alertas de spike súbito. Para dashboards de throughput: rate([5m]) mostra tendência clara. Para alertas de pico repentino de erros: irate([1m]) detecta mais rápido. Nunca use irate em intervalos longos, perde o contexto histórico.