tutorial

How to Monitor Your Hapi.js App with Vigilmon

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 pro...

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 /health route 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/hapi v21+)
  • 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

  1. Log in to vigilmon.online and click Add Monitor.
  2. Choose HTTP(S) monitor.
  3. Enter your health URL: https://yourapp.example.com/health
  4. Set the check interval to 1 minute (free tier supports 5-minute intervals; paid plans go down to 1 minute).
  5. Add an alert channel — email, Slack, or a webhook URL.
  6. 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

  1. In Vigilmon, click Add Monitor.
  2. Choose Heartbeat monitor.
  3. Set the expected interval to match your worker's cycle time (e.g., 5 minutes).
  4. Copy the unique URL: https://vigilmon.online/heartbeat/abc123xyz
  5. Set VIGILMON_HEARTBEAT_URL in 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:

  1. /health plugin — checks real dependencies (database, Redis), returns HTTP 503 when degraded so Vigilmon alerts automatically.
  2. Webhook plugin — receives Vigilmon DOWN/UP events and routes them to Slack or your on-call system with proper Hapi validation.
  3. Worker heartbeat — pings Vigilmon on each successful processing cycle; silence triggers an alert before users notice.
  4. 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

Monitor your app with Vigilmon

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

Start free →