tutorial

Bun Native HTTP Server Monitoring with Vigilmon

Bun's built-in `Bun.serve()` API gives you a fast, zero-dependency HTTP server without Express, Fastify, or any third-party framework. When your production a...

Bun's built-in Bun.serve() API gives you a fast, zero-dependency HTTP server without Express, Fastify, or any third-party framework. When your production app runs on raw Bun, you still need external uptime monitoring — your process logs won't tell you that traffic stopped arriving.

This tutorial covers monitoring a native Bun.serve() application with Vigilmon, including:

  • A health route inside Bun.serve()
  • Vigilmon HTTP and keyword monitoring
  • A heartbeat pattern for Bun's scheduled tasks

Note: This guide covers raw Bun.serve(). If you are using Elysia.js on top of Bun, the setup is similar but Elysia provides its own routing primitives.


Prerequisites

  • Bun 1.0+ (curl -fsSL https://bun.sh/install | bash)
  • A Bun.serve() application
  • A free account at vigilmon.online

Part 1: Add a health route to Bun.serve()

Bun.serve() routes are handled in the fetch function. Add a /health case before your main routing logic:

// src/server.ts
import { checkDatabase } from './db';

interface HealthStatus {
  status: 'ok' | 'degraded';
  timestamp: string;
  runtime: string;
  uptime: number;
  checks: Record<string, 'ok' | string>;
}

const startTime = Date.now();

const server = Bun.serve({
  port: process.env['PORT'] ? Number(process.env['PORT']) : 3000,

  async fetch(req) {
    const url = new URL(req.url);

    // --- Health check ---
    if (url.pathname === '/health' && req.method === 'GET') {
      const checks: Record<string, 'ok' | string> = {};
      let degraded = false;

      try {
        await checkDatabase(); // Replace with your real dependency check
        checks.database = 'ok';
      } catch (err) {
        checks.database = (err as Error).message;
        degraded = true;
      }

      const body: HealthStatus = {
        status: degraded ? 'degraded' : 'ok',
        timestamp: new Date().toISOString(),
        runtime: 'bun',
        uptime: (Date.now() - startTime) / 1000,
        checks,
      };

      return new Response(JSON.stringify(body), {
        status: degraded ? 503 : 200,
        headers: { 'Content-Type': 'application/json' },
      });
    }

    // --- Your application routes ---
    if (url.pathname === '/' && req.method === 'GET') {
      return new Response('Hello from Bun!');
    }

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

  error(err) {
    console.error('Server error:', err);
    return new Response('Internal Server Error', { status: 500 });
  },
});

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

Stub out checkDatabase for local testing:

// src/db.ts
export async function checkDatabase(): Promise<void> {
  // Replace with actual connection test, e.g.:
  // await pool.query('SELECT 1');
  if (!process.env['DATABASE_URL']) {
    throw new Error('DATABASE_URL not configured');
  }
}

Test it:

bun run src/server.ts
curl http://localhost:3000/health
{
  "status": "ok",
  "timestamp": "2026-06-30T10:00:00.000Z",
  "runtime": "bun",
  "uptime": 5.1,
  "checks": { "database": "ok" }
}

Part 2: Set up HTTP monitoring in Vigilmon

  1. Log in to vigilmon.online and click Add Monitor.
  2. Choose HTTP(S) monitor.
  3. URL: https://yourapp.example.com/health
  4. Interval: 1 minute.
  5. Under Keyword assertion, enter "status":"ok" so Vigilmon flags a degraded response even when the HTTP code is 200.
  6. Add an alert channel (email, Slack, or webhook).
  7. Click Save.

Vigilmon polls from multiple geographic regions simultaneously. It fires an alert only when a quorum of probe locations agree the endpoint is down — this eliminates false positives from regional network blips.


Part 3: Webhook alert endpoint

Add a webhook route so Vigilmon notifies your team on DOWN/UP transitions:

// Inside your Bun.serve() fetch handler
if (url.pathname === '/webhook/vigilmon' && req.method === 'POST') {
  const body = await req.json() as {
    monitor_name: string;
    status: 'down' | 'up';
    url: string;
    response_code: number;
    checked_at: string;
  };

  if (body.status === 'down') {
    console.error('[VIGILMON] DOWN', body.monitor_name, body.url, body.response_code);

    const slack = process.env['SLACK_WEBHOOK_URL'];
    if (slack) {
      await fetch(slack, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          text: `*DOWN*: ${body.monitor_name} — ${body.url} (${body.response_code})`,
        }),
      });
    }
  } else {
    console.info('[VIGILMON] Recovered', body.monitor_name);
  }

  return new Response(null, { status: 204 });
}

In Vigilmon, add a Webhook alert channel pointing at https://yourapp.example.com/webhook/vigilmon.


Part 4: Heartbeat monitoring for Bun cron tasks

Bun 1.1+ ships Bun.cron() for scheduled tasks. Use a Vigilmon heartbeat monitor to detect when a cron task silently stops running.

Create the heartbeat monitor

  1. In Vigilmon, click Add MonitorHeartbeat.
  2. Name it something like nightly-cleanup.
  3. Set expected interval to match your task (e.g., 60 minutes for hourly tasks).
  4. Copy the heartbeat URL.

Ping from your cron task

// src/cron.ts
const HEARTBEAT_URL = process.env['VIGILMON_HEARTBEAT_URL'];

async function pingHeartbeat(): Promise<void> {
  if (!HEARTBEAT_URL) return;
  try {
    const res = await fetch(HEARTBEAT_URL, { signal: AbortSignal.timeout(5_000) });
    if (!res.ok) console.warn(`Heartbeat returned ${res.status}`);
  } catch (err) {
    console.warn('Heartbeat ping failed:', err);
  }
}

// Register cron task — pings only on success
Bun.cron('0 * * * *', async () => {
  try {
    await runCleanupTask();
    await pingHeartbeat(); // Only reached if task succeeds
  } catch (err) {
    console.error('Cron task failed:', err);
    // No ping — Vigilmon alerts after the expected interval passes
  }
});

async function runCleanupTask(): Promise<void> {
  console.log('Running cleanup...');
  // Your actual task logic
}

If the task crashes or the Bun process itself exits, the heartbeat stops. Vigilmon fires an alert after the grace period expires.


Running in production

Bun supports process management via --hot during development, but production deployments typically use a process manager or container:

# systemd unit (Linux)
# Restart on crash, which resets the uptime counter
ExecStart=/usr/local/bin/bun run /app/src/server.ts
Restart=always

Or a minimal Dockerfile:

FROM oven/bun:1
WORKDIR /app
COPY . .
RUN bun install --production
CMD ["bun", "run", "src/server.ts"]

Summary

| Scenario | Vigilmon monitor type | |---|---| | /health route responds | HTTP monitor with keyword "status":"ok" | | Cron task runs on schedule | Heartbeat monitor with pingHeartbeat() | | DOWN/UP alerts to team | Webhook → Slack |

Raw Bun.serve() is lean by design. Adding Vigilmon gives you the external observability layer without adding framework overhead to your application.

Monitor your app with Vigilmon

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

Start free →