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
- Log in to vigilmon.online and click Add Monitor.
- Choose HTTP(S) monitor.
- URL:
https://yourapp.example.com/health - Interval: 1 minute.
- Under Keyword assertion, enter
"status":"ok"so Vigilmon flags a degraded response even when the HTTP code is 200. - Add an alert channel (email, Slack, or webhook).
- 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
- In Vigilmon, click Add Monitor → Heartbeat.
- Name it something like
nightly-cleanup. - Set expected interval to match your task (e.g., 60 minutes for hourly tasks).
- 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.