Skip to main content
Monitors are TypeScript files that define health checks for your services. Each monitor exports a configuration object that specifies what to check, how often to check it, and what to do with the results.

What are Monitors?

Monitors are the core building blocks of Pongo. They:
  • Run automated health checks on your services
  • Track response times and availability
  • Store historical data for uptime analysis
  • Trigger alerts when conditions are met
  • Support custom logic via async handlers

MonitorConfig Interface

Every monitor must conform to the MonitorConfig interface:
export interface MonitorConfig {
  name: string;
  interval?: string; // human-readable: "30s", "5m", "1h" - optional if cron is set
  cron?: string; // cron expression: "*/5 * * * *" - optional if interval is set
  timeout?: string; // human-readable, defaults to "30s"
  active?: boolean; // defaults to true
  alerts?: AlertConfig[];

  /**
   * Handler function that runs the monitor check
   * Returns status, response time, and optional message
   */
  handler: () => Promise<MonitorResult>;
}
name
string
required
Display name for the monitor
interval
string
How often to run the check. Supports human-readable format: "30s", "5m", "1h", "1d". Optional if cron is set.
cron
string
Cron expression for scheduling. Example: "*/5 * * * *" (every 5 minutes). Optional if interval is set.
timeout
string
default:"30s"
Maximum time to wait for the handler to complete. Uses human-readable format.
active
boolean
default:"true"
Whether the monitor is enabled. Set to false to temporarily disable without deleting.
alerts
AlertConfig[]
Array of alert configurations to attach to this monitor. See Alerts for details.
handler
() => Promise<MonitorResult>
required
Async function that performs the actual health check and returns a result.

MonitorResult Interface

The handler function must return a MonitorResult object:
export interface MonitorResult {
  status: MonitorStatus;
  responseTime: number; // milliseconds
  message?: string;
  statusCode?: number;
}
status
'up' | 'down' | 'degraded' | 'pending'
required
Current status of the service being monitored
responseTime
number
required
Response time in milliseconds
message
string
Optional message with additional context (e.g., error details)
statusCode
number
HTTP status code if applicable

Handler Function

The handler is an async function where you implement your check logic:
async handler() {
  const start = Date.now();
  
  try {
    const res = await fetch("https://api.example.com/health");
    const responseTime = Date.now() - start;
    
    return {
      status: res.ok ? "up" : "down",
      responseTime,
      statusCode: res.status,
    };
  } catch (error) {
    return {
      status: "down",
      responseTime: Date.now() - start,
      message: error instanceof Error ? error.message : "Unknown error",
    };
  }
}
Always measure response time from start to finish, and handle errors gracefully by returning a “down” status.

Scheduling

Monitors can be scheduled using either interval or cron:

Interval-based Scheduling

Use human-readable durations:
interval: "30s"  // every 30 seconds
interval: "5m"   // every 5 minutes
interval: "1h"   // every hour
interval: "1d"   // every day

Cron-based Scheduling

Use standard cron expressions:
cron: "*/5 * * * *"    // every 5 minutes
cron: "0 */2 * * *"    // every 2 hours
cron: "0 9 * * 1-5"    // weekdays at 9am
You must specify either interval or cron, but not both.

Examples

Basic HTTP Health Check

pongo/monitors/pongo.ts
import { monitor } from "../../src/lib/config-types";

export default monitor({
  name: "Pongo",
  interval: "5m",
  timeout: "30s",

  async handler() {
    const url = process.env.PONGO_URL;
    if (!url) {
      return {
        status: "down",
        responseTime: 0,
        message: "PONGO_URL environment variable not set",
      };
    }

    const start = Date.now();

    try {
      const res = await fetch(url);
      const responseTime = Date.now() - start;

      if (!res.ok) {
        return {
          status: "down",
          responseTime,
          statusCode: res.status,
          message: `HTTP ${res.status}`,
        };
      }

      return {
        status: responseTime > 3000 ? "degraded" : "up",
        responseTime,
        statusCode: res.status,
      };
    } catch (error) {
      return {
        status: "down",
        responseTime: Date.now() - start,
        message: error instanceof Error ? error.message : "Unknown error",
      };
    }
  },
});

Third-party Status Page Integration

pongo/monitors/vercel.ts
import { monitor } from "../../src/lib/config-types";

export default monitor({
  name: "Vercel",
  interval: "15m",
  timeout: "30s",

  async handler() {
    const start = Date.now();

    try {
      const res = await fetch(
        "https://www.vercel-status.com/api/v2/status.json",
      );
      const responseTime = Date.now() - start;

      if (!res.ok) {
        return {
          status: "down",
          responseTime,
          statusCode: res.status,
          message: `HTTP ${res.status}`,
        };
      }

      const data = (await res.json()) as {
        status: { indicator: string; description: string };
      };
      const indicator = data.status.indicator;

      if (indicator === "none") {
        return {
          status: "up",
          responseTime,
          statusCode: res.status,
        };
      }

      if (indicator === "minor") {
        return {
          status: "degraded",
          responseTime,
          statusCode: res.status,
          message: data.status.description,
        };
      }

      if (indicator === "major" || indicator === "critical") {
        return {
          status: "down",
          responseTime,
          statusCode: res.status,
          message: data.status.description,
        };
      }

      return {
        status: "down",
        responseTime,
        statusCode: res.status,
        message: `Unknown indicator: ${indicator}`,
      };
    } catch (error) {
      return {
        status: "down",
        responseTime: Date.now() - start,
        message: error instanceof Error ? error.message : "Unknown error",
      };
    }
  },
});

Simple Website Check

pongo/monitors/hackernews.ts
import { monitor } from "../../src/lib/config-types";

export default monitor({
  name: "Hacker News",
  interval: "5m",
  timeout: "15s",

  async handler() {
    const start = Date.now();

    try {
      const res = await fetch("https://news.ycombinator.com/");
      const responseTime = Date.now() - start;

      if (!res.ok) {
        return {
          status: "down",
          responseTime,
          statusCode: res.status,
          message: `HTTP ${res.status}`,
        };
      }

      return {
        status: "up",
        responseTime,
        statusCode: res.status,
      };
    } catch (error) {
      return {
        status: "down",
        responseTime: Date.now() - start,
        message: error instanceof Error ? error.message : "Unknown error",
      };
    }
  },
});

File Structure

Monitors live in pongo/monitors/ and must be registered in the index file:
pongo/monitors/index.ts
import api from "./api";
import database from "./database";
import cdn from "./cdn";

export default { api, database, cdn };
The filename (without .ts) becomes the monitor ID used throughout the system.

Helper Function

Use the monitor() helper for full type inference:
import { monitor } from "../../src/lib/config-types";

export default monitor({
  name: "My Service",
  interval: "1m",
  async handler() {
    // TypeScript will validate your return type
    return {
      status: "up",
      responseTime: 123,
    };
  },
});

Next Steps

Alerts

Attach alert conditions to monitors

Dashboards

Group monitors into dashboards

Build docs developers (and LLMs) love