tutorial

Monitoring Your Nitro.js (H3) Application with Vigilmon

Add production-grade uptime monitoring to a standalone Nitro server — health endpoints, HTTP monitors, heartbeat pings for scheduled tasks, and Slack alerts.

Nitro is the server engine that powers Nuxt 3, Analog, and a growing list of full-stack frameworks. It runs on Node.js, Deno, Bun, and edge runtimes — but regardless of where it runs, it can go down. A misconfigured environment variable, a failed cold start, or a database connection failure will return errors to your users long before anyone notices. Vigilmon catches those failures from the outside and alerts you the moment they happen.

This tutorial adds production-grade monitoring to a standalone Nitro application.

What You'll Build

  • A /health API route in your Nitro app that checks real dependencies
  • A Vigilmon HTTP monitor watching your health endpoint
  • A heartbeat monitor for any Nitro scheduled tasks
  • Alert channels (email + Slack)

Prerequisites

  • A Nitro project (standalone, or embedded in Nuxt/Analog)
  • Node.js 18+ or Bun
  • A free account at vigilmon.online

Step 1: Add a Health Check Route

Nitro uses file-based routing. Create server/routes/health.get.ts (or .js for JavaScript projects):

// server/routes/health.get.ts
import { defineEventHandler, setResponseStatus } from "h3";

interface HealthCheck {
  name: string;
  status: "ok" | "error";
  detail?: string;
}

export default defineEventHandler(async (event) => {
  const checks: HealthCheck[] = [];
  let allOk = true;

  // 1. Database probe — replace with your actual DB client
  try {
    // Example for a Postgres client available via useStorage or nitropack context
    // await db.query("SELECT 1");
    checks.push({ name: "database", status: "ok" });
  } catch (e) {
    checks.push({
      name: "database",
      status: "error",
      detail: (e as Error).message,
    });
    allOk = false;
  }

  // 2. External API probe — replace with your real dependency
  try {
    const resp = await $fetch<unknown>("https://api.example.com/ping", {
      timeout: 3000,
    }).catch((err) => {
      throw err;
    });
    checks.push({ name: "upstream_api", status: "ok" });
  } catch (e) {
    checks.push({
      name: "upstream_api",
      status: "error",
      detail: (e as Error).message,
    });
    allOk = false;
  }

  // 3. Storage probe — check Nitro's built-in storage (if used)
  try {
    const storage = useStorage();
    await storage.setItem("health:probe", "1");
    const val = await storage.getItem("health:probe");
    const storageOk = val === "1";
    checks.push({
      name: "storage",
      status: storageOk ? "ok" : "error",
      detail: storageOk ? undefined : "read-after-write mismatch",
    });
    if (!storageOk) allOk = false;
  } catch (e) {
    checks.push({
      name: "storage",
      status: "error",
      detail: (e as Error).message,
    });
    allOk = false;
  }

  if (!allOk) {
    setResponseStatus(event, 503);
  }

  return {
    status: allOk ? "ok" : "degraded",
    checks,
    ts: new Date().toISOString(),
  };
});

Start your dev server and verify:

npx nitro dev
curl http://localhost:3000/health

Expected response:

{
  "status": "ok",
  "checks": [
    { "name": "database", "status": "ok" },
    { "name": "upstream_api", "status": "ok" },
    { "name": "storage", "status": "ok" }
  ],
  "ts": "2026-01-15T10:23:00.000Z"
}

When any check fails, the route returns 503 with the failing check's detail field populated. That's exactly what you want — a monitor that knows what broke, not just that something did.

Tip: If you only use Nitro as the runtime for Nuxt or Analog, the same file path (server/routes/health.get.ts) works there too. Nuxt serves it at /health automatically.


Step 2: Set Up the Vigilmon HTTP Monitor

With the health endpoint deployed to your server or hosting platform:

  1. Log in to Vigilmon and click New Monitor → HTTP.
  2. Set URL to https://yourapp.example.com/health.
  3. Set Check interval to 60 seconds.
  4. Set Expected status code to 200.
  5. Under Advanced → JSON body assertion:
    • Path: status
    • Expected value: ok
  6. Save.

Vigilmon polls your endpoint from multiple external regions. If it returns a non-200 or status != "ok", you get alerted within the check interval.

You can add extra monitors for other critical routes:

https://yourapp.example.com/api/ping       → lightweight alive check
https://yourapp.example.com/health         → full dependency check
https://yourapp.example.com/api/v1/status  → API versioning alive

Each monitor is independent, so a failure on one doesn't mask a failure on another.


Step 3: Alert Channels

In Vigilmon, go to Notifications → New Channel:

  • Email — on-call address for immediate paging
  • Slack webhook — post to your team channel

When your Nitro app goes down:

🔴 DOWN: yourapp.example.com/health
Status: 503 Service Unavailable
Region: AP-Southeast
2 minutes ago

When it recovers:

✅ RECOVERED: yourapp.example.com/health
Downtime: 4 minutes

Step 4: Heartbeat Monitoring for Nitro Scheduled Tasks

Nitro supports scheduled tasks via nitro.config.ts. If a scheduled task stops running silently — crashes, hangs, or simply isn't triggered by the host — your HTTP health endpoint stays green while work stops happening.

The heartbeat pattern: ping a unique Vigilmon URL at the end of each successful run. No ping within the grace period → alert.

Configure a task in nitro.config.ts:

// nitro.config.ts
export default defineNitroConfig({
  experimental: {
    tasks: true,
  },
});

Create server/tasks/cleanup.ts:

// server/tasks/cleanup.ts
export default defineTask({
  meta: {
    name: "cleanup",
    description: "Nightly database cleanup",
  },
  async run({ payload, context }) {
    await performCleanup();

    // Ping Vigilmon heartbeat after successful run
    const heartbeatUrl = process.env.VIGILMON_CLEANUP_HEARTBEAT;
    if (heartbeatUrl) {
      await $fetch(heartbeatUrl, { method: "POST" }).catch(() => {
        // Log but don't rethrow — heartbeat failure shouldn't fail the task
        console.error("Failed to ping Vigilmon heartbeat");
      });
    }

    return { result: "success" };
  },
});

async function performCleanup() {
  // your cleanup logic here
}

Set the environment variable:

VIGILMON_CLEANUP_HEARTBEAT=https://vigilmon.online/api/heartbeat/<your-id>

In Vigilmon, create a Heartbeat Monitor:

  1. Click New Monitor → Heartbeat
  2. Set expected interval to match your task schedule (e.g., 25 hours for a nightly job)
  3. Copy the ping URL into your environment config

If the task crashes or its host scheduler stops triggering it, Vigilmon alerts you after one missed interval.


Step 5: Protecting the Health Endpoint in Production

If your Nitro app sits behind authentication middleware, the /health route needs to bypass it. Add a middleware exception in server/middleware/auth.ts:

// server/middleware/auth.ts
export default defineEventHandler((event) => {
  // Skip auth for the health check
  if (event.path === "/health") return;

  // ... your auth logic
});

Alternatively, protect the health endpoint itself with a secret token that you configure in Vigilmon as a custom request header:

// server/routes/health.get.ts (protected variant)
export default defineEventHandler(async (event) => {
  const token = getHeader(event, "x-health-token");
  if (token !== process.env.HEALTH_CHECK_TOKEN) {
    setResponseStatus(event, 401);
    return { error: "unauthorized" };
  }
  // ... rest of health check
});

In Vigilmon, add a Custom header to the monitor: x-health-token: <your-secret>.


Step 6: Status Page

Go to Status Pages → New Status Page in Vigilmon. Add your monitors, set a name, and share the public URL. Embed the badge in your project README:

![API Status](https://vigilmon.online/badge/<monitor-slug>)

What You've Built

| What | How | |---|---| | Health endpoint | Nitro file-based route at /health | | Dependency checks | Database, upstream API, Nitro storage | | HTTP uptime monitoring | Vigilmon HTTP monitor + JSON body assertion | | Alert channels | Email + Slack via Vigilmon notification channels | | Scheduled task monitoring | Heartbeat ping in Nitro task handler | | Status page | Vigilmon public status page + badge |


Nitro's flexibility is its strength — it runs everywhere. That same flexibility means there's no single place to see whether all your deployments are healthy. Vigilmon fills that gap with an external monitor that doesn't care what runtime you're on.

Monitor your Nitro app today — register free at vigilmon.online.

Monitor your app with Vigilmon

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

Start free →