You deployed your Hapi.js API. The server is running, but how quickly do you know when a route handler throws or a dependency goes silent at 3am? Default process monitoring only tells you the process is alive — it says nothing about whether your HTTP layer is healthy or your database is responding.
This tutorial covers production uptime monitoring for Hapi.js applications using Vigilmon. We will walk through:
- A
/healthroute with real dependency checks - Vigilmon HTTP monitoring setup and alert configuration
- Webhook alerts for DOWN/UP events
- A background heartbeat for scheduled job workers
Prerequisites
- Node.js 18+
- An existing Hapi.js application (
@hapi/hapiv21+) - A free account at vigilmon.online
Part 1: Add a health check route
Hapi uses a different route registration pattern than Express. Here is a proper health plugin you can register on any server.
Basic health plugin
// plugins/health.js
'use strict';
const healthPlugin = {
name: 'health',
version: '1.0.0',
register: async (server) => {
server.route({
method: 'GET',
path: '/health',
options: {
auth: false, // always public — monitoring must reach this
tags: ['api', 'health'],
},
handler: async (request, h) => {
const checks = {};
let status = 'ok';
// Check database
try {
await request.server.app.db.query('SELECT 1');
checks.database = 'ok';
} catch (err) {
checks.database = `error: ${err.message}`;
status = 'degraded';
}
// Check Redis if present
if (request.server.app.redis) {
try {
await request.server.app.redis.ping();
checks.redis = 'ok';
} catch (err) {
checks.redis = `error: ${err.message}`;
status = 'degraded';
}
}
const code = status === 'ok' ? 200 : 503;
return h.response({
status,
timestamp: new Date().toISOString(),
uptime: process.uptime(),
checks,
}).code(code);
},
});
},
};
module.exports = healthPlugin;
Register it in your server file:
// server.js
'use strict';
const Hapi = require('@hapi/hapi');
const healthPlugin = require('./plugins/health');
const init = async () => {
const server = Hapi.server({
port: process.env.PORT || 3000,
host: '0.0.0.0',
});
// Attach shared dependencies
server.app.db = await createDatabaseConnection();
// Register plugins
await server.register(healthPlugin);
await server.start();
console.log(`Server running on ${server.info.uri}`);
};
init().catch(err => {
console.error(err);
process.exit(1);
});
Test it locally:
curl http://localhost:3000/health
{
"status": "ok",
"timestamp": "2026-06-29T08:00:00.000Z",
"uptime": 42.7,
"checks": {
"database": "ok",
"redis": "ok"
}
}
When any dependency check fails the handler returns HTTP 503, which Vigilmon immediately flags as a failure and fires an alert.
Using Hapi's lifecycle for auth exclusion
If you use an authentication strategy globally, exclude the health route explicitly with auth: false in the route options (shown above). Vigilmon sends unauthenticated requests — the health route must always be reachable without credentials.
Part 2: Set up HTTP monitoring in Vigilmon
- Log in to vigilmon.online and click Add Monitor.
- Choose HTTP(S) monitor.
- Enter your health URL:
https://yourapp.example.com/health - Set the check interval to 1 minute (free tier supports 5-minute intervals; paid plans go down to 1 minute).
- Add an alert channel — email, Slack, or a webhook URL.
- Click Save.
Vigilmon checks your endpoint from multiple regions. A majority of regions must agree the endpoint is down before an alert fires, which eliminates single-region false positives — this multi-region consensus is the key differentiator from single-location checkers.
Configure expected response
In the Vigilmon monitor settings you can optionally assert on the response body. Set expected keyword to "status":"ok" so that a degraded response (HTTP 200 with status: "degraded") also triggers an alert.
Part 3: Receive webhook alerts
Add a webhook endpoint to your Hapi app so Vigilmon events fan out to Slack, PagerDuty, or your on-call system:
// plugins/webhooks.js
'use strict';
const Joi = require('joi');
const webhookPlugin = {
name: 'webhooks',
version: '1.0.0',
register: async (server) => {
server.route({
method: 'POST',
path: '/webhooks/vigilmon',
options: {
auth: false,
validate: {
payload: Joi.object({
monitor_id: Joi.string().required(),
monitor_name: Joi.string().required(),
status: Joi.string().valid('up', 'down').required(),
url: Joi.string().uri().required(),
response_code: Joi.number().integer().optional(),
response_time_ms: Joi.number().optional(),
checked_at: Joi.string().isoDate().required(),
}).unknown(true),
},
},
handler: async (request, h) => {
const { monitor_name, status, url, response_code, checked_at } = request.payload;
if (status === 'down') {
request.server.log(['vigilmon', 'alert'], {
msg: 'Monitor DOWN',
monitor: monitor_name,
url,
code: response_code,
at: checked_at,
});
await notifyOnCall({ monitor: monitor_name, url, code: response_code });
} else {
request.server.log(['vigilmon', 'info'], {
msg: 'Monitor recovered',
monitor: monitor_name,
});
}
return h.response().code(204);
},
});
},
};
async function notifyOnCall({ monitor, url, code }) {
const slackUrl = process.env.SLACK_WEBHOOK_URL;
if (!slackUrl) return;
await fetch(slackUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
text: `*ALERT*: ${monitor} is DOWN\nURL: ${url}\nHTTP: ${code ?? 'timeout'}`,
}),
});
}
module.exports = webhookPlugin;
Register it alongside your health plugin:
await server.register([healthPlugin, webhookPlugin]);
In your Vigilmon dashboard, go to the monitor's Alert channels settings and add a webhook pointing to https://yourapp.example.com/webhooks/vigilmon.
Vigilmon sends the following payload on every DOWN and UP transition:
{
"monitor_id": "mon_abc123",
"monitor_name": "Hapi API /health",
"status": "down",
"url": "https://yourapp.example.com/health",
"checked_at": "2026-06-29T08:01:00Z",
"response_code": 503,
"response_time_ms": 1102
}
Part 4: Background job heartbeat
HTTP monitoring confirms your web tier is responsive. It will not catch a background worker that is running but stuck — for example, a Bull or BullMQ job processor that has crashed inside its handler without taking the main process down.
The heartbeat pattern closes this gap: the worker pings a Vigilmon heartbeat URL on each successful processing cycle. If the pings stop within the expected window, Vigilmon fires an alert.
Create the heartbeat monitor in Vigilmon
- In Vigilmon, click Add Monitor.
- Choose Heartbeat monitor.
- Set the expected interval to match your worker's cycle time (e.g., 5 minutes).
- Copy the unique URL:
https://vigilmon.online/heartbeat/abc123xyz - Set
VIGILMON_HEARTBEAT_URLin your production environment.
Worker implementation
// workers/processor.js
'use strict';
const HEARTBEAT_URL = process.env.VIGILMON_HEARTBEAT_URL;
const INTERVAL_MS = 5 * 60 * 1000;
async function processJobs() {
// Your actual job-processing logic here
const count = await fetchAndProcessPending();
console.log(`Processed ${count} jobs`);
}
async function pingHeartbeat() {
if (!HEARTBEAT_URL) return;
try {
const res = await fetch(HEARTBEAT_URL, { signal: AbortSignal.timeout(10_000) });
if (!res.ok) console.warn(`Heartbeat returned ${res.status}`);
} catch (err) {
console.warn('Heartbeat ping failed:', err.message);
}
}
async function run() {
while (true) {
try {
await processJobs();
await pingHeartbeat(); // only ping on success
} catch (err) {
console.error('Processing error (heartbeat skipped):', err);
// Deliberately do NOT ping — silence triggers the Vigilmon alert
}
await new Promise(resolve => setTimeout(resolve, INTERVAL_MS));
}
}
run().catch(err => {
console.error('Worker fatal:', err);
process.exit(1);
});
Part 5: Multi-region alert configuration
Vigilmon checks from multiple geographic regions and uses consensus before alerting. Configure this in your monitor's advanced settings:
- Consensus threshold: require at least 2 out of 3 regions to confirm a failure before alerting (eliminates single-CDN blips).
- Alert delay: wait for 2 consecutive failures before paging on-call (prevents noisy transient alerts).
- Recovery delay: confirm recovery from 2 consecutive successes before sending an UP notification.
For Hapi.js applications running in a single region, this multi-region check catches DNS or network issues upstream of your server that would otherwise go undetected until a user reports a problem.
Summary
Your Hapi.js application now has layered production monitoring:
/healthplugin — checks real dependencies (database, Redis), returns HTTP 503 when degraded so Vigilmon alerts automatically.- Webhook plugin — receives Vigilmon DOWN/UP events and routes them to Slack or your on-call system with proper Hapi validation.
- Worker heartbeat — pings Vigilmon on each successful processing cycle; silence triggers an alert before users notice.
- Multi-region consensus — Vigilmon eliminates false positives by requiring agreement across regions.
Vigilmon's free tier supports up to 5 monitors with 5-minute check intervals — enough to cover your main API, a background worker, and a cron job from day one.
Monitor your Hapi.js app free at vigilmon.online
#hapijs #javascript #monitoring #devops