DEV Community

Vigilmon
Vigilmon

Posted on

Uptime Monitoring for Bun Applications with Vigilmon

Uptime Monitoring for Bun Applications with Vigilmon

Bun's blazing startup speed and drop-in Node.js compatibility make it an appealing runtime for microservices and APIs. But speed at startup doesn't prevent outages at runtime. This tutorial walks you through adding a production-grade health endpoint to a Bun HTTP server and monitoring it with Vigilmon.


What You'll Build

  • A Bun HTTP server with a /health route that reflects real app state
  • A Vigilmon monitor with 5-minute polling from multiple regions
  • Webhook alerts wired to Slack (plus email fallback)

Prerequisites

  • Bun v1.0+ installed
  • A free Vigilmon account (5 monitors free, no credit card)

Step 1: Scaffold a Bun HTTP Server

Create a new project:

mkdir bun-monitored-app && cd bun-monitored-app
bun init -i
Enter fullscreen mode Exit fullscreen mode

Replace index.ts with a basic HTTP server:

// index.ts
const PORT = Number(process.env.PORT ?? 3000);

const server = Bun.serve({
  port: PORT,
  async fetch(req) {
    const url = new URL(req.url);

    switch (url.pathname) {
      case "/":
        return new Response("Hello from Bun!", { status: 200 });

      case "/health":
        return healthHandler();

      default:
        return new Response("Not Found", { status: 404 });
    }
  },
});

console.log(`Server running at http://localhost:${server.port}`);
Enter fullscreen mode Exit fullscreen mode

Add the health handler in the same file (or a separate module):

function healthHandler(): Response {
  const payload = JSON.stringify({
    status: "ok",
    runtime: "bun",
    version: Bun.version,
    timestamp: new Date().toISOString(),
  });
  return new Response(payload, {
    status: 200,
    headers: { "Content-Type": "application/json" },
  });
}
Enter fullscreen mode Exit fullscreen mode

Start it:

bun run index.ts
Enter fullscreen mode Exit fullscreen mode

Verify:

curl http://localhost:3000/health
# {"status":"ok","runtime":"bun","version":"1.0.20","timestamp":"..."}
Enter fullscreen mode Exit fullscreen mode

Step 2: Add Real Dependency Checks

A health endpoint that always returns 200 isn't actually useful for monitoring. Wire in checks for the things your app actually depends on:

// health.ts
interface CheckResult {
  ok: boolean;
  latencyMs?: number;
  error?: string;
}

async function pingDatabase(): Promise<CheckResult> {
  // Replace with your actual DB client ping
  // Example shown for a hypothetical SQLite check via bun:sqlite
  try {
    const start = performance.now();
    // const db = new Database("myapp.db");
    // db.query("SELECT 1").get();
    // Placeholder: always ok for this example
    return { ok: true, latencyMs: Math.round(performance.now() - start) };
  } catch (err) {
    return { ok: false, error: String(err) };
  }
}

async function pingCache(): Promise<CheckResult> {
  // Replace with your Redis / Bun KV check
  try {
    const start = performance.now();
    // await redisClient.ping();
    return { ok: true, latencyMs: Math.round(performance.now() - start) };
  } catch (err) {
    return { ok: false, error: String(err) };
  }
}

export async function buildHealthPayload() {
  const [db, cache] = await Promise.all([pingDatabase(), pingCache()]);

  const allOk = db.ok && cache.ok;
  return {
    status: allOk ? "ok" : "degraded",
    checks: { database: db, cache },
    timestamp: new Date().toISOString(),
    httpStatus: allOk ? 200 : 503,
  };
}
Enter fullscreen mode Exit fullscreen mode

Update the handler:

import { buildHealthPayload } from "./health";

case "/health": {
  const payload = await buildHealthPayload();
  const { httpStatus, ...body } = payload;
  return new Response(JSON.stringify(body), {
    status: httpStatus,
    headers: { "Content-Type": "application/json" },
  });
}
Enter fullscreen mode Exit fullscreen mode

Vigilmon treats any non-2xx response as a downtime event, so returning 503 when a check fails will trigger an alert automatically.


Step 3: Deploy Your Bun App

Your app needs a public URL before Vigilmon can monitor it.

Fly.io (works great with Bun):

# fly.toml
app = "my-bun-app"
primary_region = "iad"

[http_service]
  internal_port = 3000
  force_https = true

[[vm]]
  memory = "256mb"
  cpu_kind = "shared"
  cpus = 1
Enter fullscreen mode Exit fullscreen mode
fly launch
fly deploy
Enter fullscreen mode Exit fullscreen mode

Railway / Render also work with zero config — just point them at your repo and set bun run index.ts as the start command.

Note your production URL (e.g. https://my-bun-app.fly.dev).


Step 4: Create a Vigilmon Monitor

  1. Sign in at vigilmon.online.
  2. Click + New Monitor.
  3. Choose HTTP / HTTPS as the monitor type.
  4. Enter your health URL: https://my-bun-app.fly.dev/health.
  5. Set Check Interval to 5 minutes.
  6. Leave Expected HTTP Status as 2xx.
  7. Click Save Monitor.

Vigilmon starts polling from multiple geographic nodes simultaneously. A downtime event is only recorded when a consensus of regions fails — single-location network hiccups won't wake you at 3 AM.


Step 5: Set Up Webhook Alerts

Slack Integration

  1. Create a Slack Incoming Webhook for your alerts channel.
  2. In Vigilmon: Settings → Alert Channels → + Add Channel → Webhook.
  3. Name it "Slack Alerts", paste the webhook URL, save.
  4. Open your monitor → Edit → Alert Channels → select "Slack Alerts".

You'll now receive a Slack message like:

🔴 my-bun-app /health is DOWN

Reason: HTTP 503 — {"status":"degraded","checks":{"database":{"ok":false},...}}

Checked at: 2024-01-15 10:31 UTC

And a recovery message when it comes back up:

🟢 my-bun-app /health is back UP (was down 4m 32s)

Email Alerts

Settings → Alert Channels → + Add Channel → Email. Enter your address and assign it to the monitor. Email is a good fallback even if you already have Slack.


Step 6: Validate the Alert Pipeline

Force a failure locally or in staging to confirm alerts fire end-to-end:

// Temporarily break the health endpoint
case "/health":
  return new Response(JSON.stringify({ status: "down", reason: "test" }), {
    status: 503,
    headers: { "Content-Type": "application/json" },
  });
Enter fullscreen mode Exit fullscreen mode

Deploy this, wait for the next check cycle (≤ 5 min), and verify the Slack/email notification arrives. Then revert and confirm the recovery alert.


Step 7: Protect the Health Endpoint (Optional)

If you don't want the health response (which may reveal internal dependency states) to be publicly readable, you can add token-based gating:

case "/health": {
  const token = req.headers.get("x-health-token");
  if (token !== process.env.HEALTH_TOKEN) {
    return new Response("Unauthorized", { status: 401 });
  }
  const payload = await buildHealthPayload();
  // ...
}
Enter fullscreen mode Exit fullscreen mode

In Vigilmon, set a custom request header under Monitor → Edit → Request Headers:

Header Value
x-health-token your-secret-token

Bun-Specific Tips

Hot reload in dev: Use bun --hot run index.ts during development so code changes reload without restarting — but always test the health endpoint after a hot reload to confirm handlers rewired correctly.

Bun macros: If you use Bun's compile-time macros, make sure your health handler doesn't accidentally import macro-only modules that won't be available at runtime.

Built-in SQLite: Bun ships bun:sqlite — if your app uses it, a SELECT 1 ping in the health check is zero-overhead and catches "database file locked or corrupted" failures early.


Summary

In under 15 minutes you've added production-grade observability to a Bun application:

  1. /health endpoint with real dependency checks (returns 503 on failure)
  2. Vigilmon polling every 5 minutes from multiple regions
  3. Slack + email alerts on down/up state changes

Start free at vigilmon.online — no credit card, up to 5 monitors, 5-minute check intervals.


Building something interesting with Bun? Share what you're monitoring in the comments.

Top comments (0)