tutorial

Monitoring Your Cycle.js Application with Vigilmon: Health Checks, Heartbeats & Uptime Alerts

Add production-grade monitoring to a Cycle.js application — a health API, background job heartbeats, and instant alerts when your reactive streams or backend go down.

Cycle.js takes a uniquely functional and reactive approach to UI development — everything is a stream of observable events, wired together through pure functions and RxJS. But when your app goes down or your backend streams stop flowing, no amount of reactive elegance helps. Vigilmon gives you the observability you need: continuous HTTP monitoring, heartbeat checks, and instant alerts via email or Slack.

What You'll Build

  • A health API endpoint alongside your Cycle.js frontend
  • Vigilmon HTTP monitors for your SPA and backend
  • A heartbeat for scheduled background tasks
  • Alert channels (email and Slack)
  • An uptime badge rendered in your Cycle.js DOM driver

Prerequisites

  • A Cycle.js project (with @cycle/run, @cycle/dom, rxjs)
  • Node.js/Express backend for the health endpoint
  • A free Vigilmon account

Step 1: Add a Health Endpoint

Cycle.js runs in the browser, so health checks live on your Node/Express backend. Add a /health route:

// server.js (Express)
import express from "express";

const app = express();

app.get("/health", async (req, res) => {
  const checks = {};
  let degraded = false;

  // Database check
  try {
    // await db.query("SELECT 1");
    checks.database = "ok";
  } catch (err) {
    checks.database = `error: ${err.message}`;
    degraded = true;
  }

  // Message broker / event stream check
  try {
    // await messageBroker.ping();
    checks.messageBroker = "ok";
  } catch (err) {
    checks.messageBroker = `error: ${err.message}`;
    degraded = true;
  }

  res.status(degraded ? 503 : 200).json({
    status: degraded ? "degraded" : "ok",
    timestamp: new Date().toISOString(),
    checks,
  });
});

app.listen(process.env.PORT ?? 3000);

Test it:

curl http://localhost:3000/health | jq .
# {"status":"ok","timestamp":"2026-07-03T...","checks":{"database":"ok","messageBroker":"ok"}}

Step 2: Configure Vigilmon HTTP Monitors

Log in to VigilmonMonitors → New Monitor:

Monitor 1: Cycle.js SPA

| Field | Value | |---|---| | URL | https://yourapp.com/ | | Method | GET | | Expected status | 200 | | Expected body | A stable string from your HTML (e.g. <div id="app"> or page title) | | Check interval | 1 minute |

Monitor 2: Health API

| Field | Value | |---|---| | URL | https://yourapp.com/health | | Method | GET | | Expected status | 200 | | Check interval | 1 minute |

Two monitors cover two independent failure planes: your CDN/static serving layer and your backend data layer.


Step 3: Heartbeat for Background Tasks

Cycle.js's reactive philosophy pairs naturally with stream-based background workers — but those workers can stall silently. A Vigilmon heartbeat catches this by alerting when the expected ping never arrives.

npm install node-cron rxjs
// scheduler.js
import cron from "node-cron";
import { from, catchError, EMPTY } from "rxjs";

const VIGILMON_HEARTBEAT_URL = process.env.VIGILMON_HEARTBEAT_URL ?? "";

async function pingHeartbeat() {
  if (!VIGILMON_HEARTBEAT_URL) return;
  try {
    await fetch(VIGILMON_HEARTBEAT_URL, { signal: AbortSignal.timeout(5000) });
  } catch {
    // Monitoring must never crash the job
  }
}

cron.schedule("*/10 * * * *", () => {
  from(processStreamEvents())
    .pipe(catchError((err) => {
      console.error("[scheduler] Stream processing failed:", err);
      return EMPTY;
    }))
    .subscribe({
      complete: () => pingHeartbeat(),
    });
});

async function processStreamEvents() {
  // Your actual stream processing logic
}

Set up the heartbeat in Vigilmon:

  1. Monitors → New Heartbeat Monitor
  2. Set Expected interval to 20 minutes (2× the cron interval)
  3. Copy the ping URL to your .env:
VIGILMON_HEARTBEAT_URL=https://vigilmon.online/api/heartbeat/xxxxxxxx

Step 4: Display Uptime Status in Your Cycle.js DOM

In Cycle.js, side effects (like fetching the badge) go through drivers. Here's a minimal approach using the HTTP driver and DOM driver:

// src/StatusBadge.js
import { div, a, img } from "@cycle/dom";
import { map, startWith, switchMap } from "rxjs/operators";
import { of } from "rxjs";

const BADGE_URL = "https://vigilmon.online/api/badge/your-monitor-id.svg";
const STATUS_URL = "https://vigilmon.online/status/your-monitor-slug";

export function StatusBadge(sources) {
  // Image loaded state via DOM events
  const badgeLoaded$ = sources.DOM
    .select(".uptime-badge")
    .events("load")
    .pipe(
      map(() => true),
      startWith(false)
    );

  const vdom$ = badgeLoaded$.pipe(
    map((loaded) =>
      a(
        {
          attrs: {
            href: STATUS_URL,
            target: "_blank",
            rel: "noopener noreferrer",
            "aria-label": "Service uptime",
          },
        },
        img({
          attrs: { src: BADGE_URL, alt: "Uptime", width: 120, height: 20 },
          class: { "uptime-badge": true },
          style: { opacity: loaded ? "1" : "0", transition: "opacity 0.2s" },
        })
      )
    )
  );

  return { DOM: vdom$ };
}

Compose it into your main main.js:

// src/main.js
import { run } from "@cycle/run";
import { makeDOMDriver, div, main, footer } from "@cycle/dom";
import { StatusBadge } from "./StatusBadge";

function App(sources) {
  const badge = StatusBadge(sources);

  const vdom$ = badge.DOM.pipe(
    map((badgeVdom) =>
      div([
        main(/* your content */),
        footer([badgeVdom]),
      ])
    )
  );

  return { DOM: vdom$ };
}

run(App, {
  DOM: makeDOMDriver("#app"),
});

Step 5: Set Up Alert Channels

In Vigilmon's Alert Channels settings:

Email

  1. Alert Channels → Email → add your on-call email
  2. Attach to both HTTP monitors and the heartbeat

Slack

  1. Create a Slack Incoming Webhook for #alerts
  2. Alert Channels → Webhook → paste the URL
  3. Payload template:
{
  "text": "🚨 *{{ monitor.name }}* is DOWN\n{{ monitor.url }}\nStatus: {{ event.status }}\n<https://vigilmon.online|Open Vigilmon>"
}

Step 6: Verify Everything Works

# 1. Health endpoint
curl -s https://yourapp.com/health | jq .

# 2. Break the DB connection string → confirm health returns 503
# Expected: Vigilmon alerts within 2 minutes

# 3. Remove VIGILMON_HEARTBEAT_URL → let the scheduler run without pinging
# Expected: heartbeat alert fires after the grace window expires

# 4. Vigilmon → "Test Alert" → confirm delivery to Slack/email

Summary

| Monitor | What It Catches | |---|---| | HTTP SPA check | CDN failures, broken static deploys | | HTTP health API | Database, message broker, and third-party API failures | | Heartbeat | Scheduler stalls, silent stream processing failures |


Next Steps

  • Create separate monitors for staging vs. production environments
  • Use Vigilmon's response time graphs to track API latency and correlate with observable stream backpressure
  • Configure multi-channel alerting: email for low-severity, Slack for critical incidents

Ready to get started? Sign up for Vigilmon free — have your first monitor running in under 5 minutes.

Monitor your app with Vigilmon

Free plan — 5 monitors, no credit card required. Up and running in 60 seconds.

Start free →