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 Vigilmon → Monitors → 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:
- Monitors → New Heartbeat Monitor
- Set Expected interval to 20 minutes (2× the cron interval)
- 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:
- Alert Channels → Email → add your on-call email
- Attach to both HTTP monitors and the heartbeat
Slack
- Create a Slack Incoming Webhook for
#alerts - Alert Channels → Webhook → paste the URL
- 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.