tutorial

Encore.ts Framework Monitoring with Vigilmon

Encore.ts is a TypeScript backend framework with a distinctive premise: you declare your infrastructure (APIs, databases, queues, secrets) as code, and Encor...

Encore.ts is a TypeScript backend framework with a distinctive premise: you declare your infrastructure (APIs, databases, queues, secrets) as code, and Encore provisions it — whether locally in the dev environment or deployed to Encore Cloud. The framework generates your API surface from TypeScript function signatures, which means you get automatic client libraries and type-safe interfaces for free.

When Encore.ts applications run in production — whether on Encore Cloud or self-hosted — they still need external uptime monitoring. Encore's own dashboards tell you about deployment health; Vigilmon tells you what external users experience.


Prerequisites

  • Node.js 18+ with encore CLI installed (npm install -g encore.dev)
  • An Encore.ts backend project
  • A free account at vigilmon.online

Part 1: Add a health service

In Encore.ts, every API group is a service. Create a dedicated health service:

// health/health.ts
import { api } from 'encore.dev/api';

interface HealthCheck {
  name: string;
  status: 'ok' | 'error';
  message?: string;
}

interface HealthResponse {
  status: 'ok' | 'degraded';
  timestamp: string;
  service: string;
  checks: HealthCheck[];
}

// Encore declares this as an unauthenticated GET endpoint
export const check = api(
  { expose: true, method: 'GET', path: '/health', auth: false },
  async (): Promise<HealthResponse> => {
    const checks: HealthCheck[] = [];
    let degraded = false;

    // Check a database connection (Encore injects DB clients automatically)
    try {
      // Replace with your actual Encore database import and query
      // const row = await db.queryRow`SELECT 1 as ok`;
      checks.push({ name: 'database', status: 'ok' });
    } catch (err) {
      checks.push({ name: 'database', status: 'error', message: String(err) });
      degraded = true;
    }

    return {
      status: degraded ? 'degraded' : 'ok',
      timestamp: new Date().toISOString(),
      service: 'health',
      checks,
    };
  }
);

Encore auto-generates the REST endpoint at GET /health. The auth: false setting ensures Vigilmon can reach it without an authentication token.

Test locally:

encore run
curl http://localhost:4000/health
{
  "status": "ok",
  "timestamp": "2026-06-30T10:00:00.000Z",
  "service": "health",
  "checks": [
    { "name": "database", "status": "ok" }
  ]
}

When any check fails, status becomes "degraded". Encore.ts does not natively set HTTP 503 for application-level errors via the standard api() helper — if you need a 503, throw an APIError:

import { APIError, ErrCode } from 'encore.dev/api';

// At the end of your handler:
if (degraded) {
  throw APIError.internal('Health check failed: one or more dependencies are degraded');
}

With the APIError approach, Vigilmon catches the 5xx HTTP status directly. Without it, rely on keyword assertion (Part 2 below) to detect "degraded" in the response body.


Part 2: Set up HTTP monitoring in Vigilmon

  1. Log in to vigilmon.online and click Add Monitor.
  2. Choose HTTP(S) monitor.

For Encore Cloud deployments:

  • URL: https://yourapp-production.encr.app/health (find the URL in your Encore Cloud dashboard)

For self-hosted Encore deployments:

  • URL: https://yourapp.example.com/health
  1. Interval: 1 minute.
  2. Under Keyword assertion, enter "status":"ok" — Vigilmon fails the check if this string is absent from the response body, catching the degraded state even when HTTP 200 is returned.
  3. Add your alert channel (email, Slack, or webhook URL).
  4. Click Save.

Vigilmon probes from multiple geographic regions, requiring a quorum of failures before firing an alert. This eliminates false positives from regional routing issues.


Part 3: Webhook alerting

Add a webhook endpoint to your Encore backend to receive DOWN/UP events from Vigilmon:

// health/webhook.ts
import { api, APIError, ErrCode } from 'encore.dev/api';

interface VigilmonPayload {
  monitor_name: string;
  status: 'down' | 'up';
  url: string;
  response_code: number;
  checked_at: string;
}

export const vigilmonWebhook = api(
  { expose: true, method: 'POST', path: '/webhook/vigilmon', auth: false },
  async (payload: VigilmonPayload): Promise<{ received: boolean }> => {
    if (payload.status === 'down') {
      console.error('[VIGILMON] Monitor DOWN', {
        monitor: payload.monitor_name,
        url: payload.url,
        code: payload.response_code,
        at: payload.checked_at,
      });

      // Notify via Slack, PagerDuty, etc.
      const slackUrl = process.env['SLACK_WEBHOOK_URL'];
      if (slackUrl) {
        await fetch(slackUrl, {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({
            text: `*DOWN*: ${payload.monitor_name} — ${payload.url}`,
          }),
        });
      }
    } else {
      console.info('[VIGILMON] Recovered', payload.monitor_name);
    }

    return { received: true };
  }
);

In Vigilmon, add a Webhook alert channel pointing at https://yourapp.example.com/webhook/vigilmon.


Part 4: Heartbeat monitoring for Encore cron jobs

Encore.ts has a built-in cron job system. Use a Vigilmon heartbeat monitor to detect when a cron task silently stops executing.

Define a cron job in Encore:

// jobs/cleanup.ts
import { CronJob } from 'encore.dev/cron';
import { api } from 'encore.dev/api';

// Internal endpoint the cron job calls
export const runCleanup = api(
  { expose: false },
  async (): Promise<{ cleaned: number }> => {
    const count = await performCleanup();

    // Ping Vigilmon heartbeat on success
    await pingHeartbeat();

    return { cleaned: count };
  }
);

// Encore CronJob — fires every hour
const _ = new CronJob('hourly-cleanup', {
  title: 'Hourly data cleanup',
  every: '1h',
  endpoint: runCleanup,
});

async function performCleanup(): Promise<number> {
  // Your cleanup logic
  return 0;
}

async function pingHeartbeat(): Promise<void> {
  const url = process.env['VIGILMON_HEARTBEAT_URL'];
  if (!url) return;
  try {
    await fetch(url, { signal: AbortSignal.timeout(5_000) });
  } catch (err) {
    console.warn('Heartbeat ping failed:', err);
  }
}

In Vigilmon, create a Heartbeat monitor with an expected interval of 65 minutes (5 minutes longer than the cron schedule — accounts for execution time variance). If the cron job stops firing, the heartbeat lapses and Vigilmon alerts.


Encore Cloud vs self-hosted

| Concern | Encore Cloud | Self-hosted | |---|---|---| | Health endpoint URL | https://yourapp-production.encr.app/health | https://yourapp.example.com/health | | Deployment observability | Built into Encore Cloud dashboard | Your own logging/APM | | Vigilmon monitoring | External, user-perspective uptime | Same — Vigilmon is always external | | Secrets / env vars | Managed via encore secret set | Your own secret manager |

Encore Cloud's built-in observability covers deployment metrics and traces. Vigilmon covers the question Encore Cloud cannot answer: "Is my endpoint reachable from the public internet right now, from multiple locations?"


Summary

| What to monitor | How | |---|---| | API health endpoint | HTTP monitor on /health | | Application-level errors | Keyword assertion: "status":"ok" | | Cron job execution | Heartbeat monitor + pingHeartbeat() | | Team alerts | Webhook → Slack |

Encore.ts removes infrastructure boilerplate; Vigilmon removes the monitoring blind spot. Together, they let a small team ship a production TypeScript backend with confidence.

Monitor your app with Vigilmon

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

Start free →