tutorial

Monitoring TypeScript REST APIs with Vigilmon

"A complete guide to adding uptime monitoring and health checks to TypeScript REST APIs built with Express, Fastify, or NestJS — including typed health endpoints and Vigilmon alert configuration."

Monitoring TypeScript REST APIs with Vigilmon

TypeScript gives you type safety and autocomplete. What it doesn't give you is visibility into whether your API is actually up and responding in production. For that you need uptime monitoring.

This guide walks you through adding strongly-typed health endpoints and connecting them to Vigilmon for HTTP monitoring and alerts — whether you're on Express, Fastify, or NestJS.


Why TypeScript APIs Need Dedicated Monitoring

A TypeScript compile error fails at build time. But most production failures happen at runtime:

  • Environment variables missing in the deployed container
  • Database credentials rotated and the connection pool refuses new connections
  • A third-party API your service depends on going offline
  • Memory limits hit inside a Kubernetes pod, triggering a restart loop
  • An unhandled promise rejection bringing down the Node process

None of these show up in tsc --noEmit. Uptime monitoring catches the result: your service stops returning 2xx responses.


Step 1: Define a Typed Health Response

Start with a shared type so every framework implementation stays consistent:

// src/health/types.ts
export type HealthStatus = 'ok' | 'degraded' | 'down';

export interface HealthCheck {
  name: string;
  status: HealthStatus;
  responseTimeMs?: number;
}

export interface HealthResponse {
  status: HealthStatus;
  version: string;
  uptime: number;
  checks: HealthCheck[];
}

Step 2: Implement the Health Endpoint

Express

// src/routes/health.ts
import { Router, Request, Response } from 'express';
import { HealthResponse, HealthCheck } from '../health/types';

const router = Router();

async function checkDatabase(): Promise<HealthCheck> {
  const start = Date.now();
  try {
    // Replace with your ORM/client — e.g. prisma.$queryRaw`SELECT 1`
    await db.raw('SELECT 1');
    return { name: 'database', status: 'ok', responseTimeMs: Date.now() - start };
  } catch {
    return { name: 'database', status: 'down', responseTimeMs: Date.now() - start };
  }
}

router.get('/health', async (req: Request, res: Response) => {
  const checks: HealthCheck[] = [await checkDatabase()];
  const overall = checks.every(c => c.status === 'ok') ? 'ok' : 'degraded';

  const body: HealthResponse = {
    status: overall,
    version: process.env.npm_package_version ?? 'unknown',
    uptime: Math.floor(process.uptime()),
    checks,
  };

  res.status(overall === 'ok' ? 200 : 503).json(body);
});

export default router;

Register it in your Express app:

// src/app.ts
import healthRouter from './routes/health';
app.use(healthRouter);

Fastify

// src/plugins/health.ts
import { FastifyPluginAsync } from 'fastify';
import { HealthResponse } from '../health/types';

const healthPlugin: FastifyPluginAsync = async (fastify) => {
  fastify.get<{ Reply: HealthResponse }>('/health', async (request, reply) => {
    const dbOk = await checkDatabase();

    const body: HealthResponse = {
      status: dbOk.status,
      version: process.env.npm_package_version ?? 'unknown',
      uptime: Math.floor(process.uptime()),
      checks: [dbOk],
    };

    reply.status(body.status === 'ok' ? 200 : 503).send(body);
  });
};

export default healthPlugin;
// src/server.ts
import healthPlugin from './plugins/health';
await fastify.register(healthPlugin);

NestJS

Generate a health module with @nestjs/terminus (the standard NestJS health library):

npm install @nestjs/terminus @nestjs/axios
nest generate module health
nest generate controller health
// src/health/health.controller.ts
import { Controller, Get } from '@nestjs/common';
import {
  HealthCheck,
  HealthCheckService,
  TypeOrmHealthIndicator,
  HealthCheckResult,
} from '@nestjs/terminus';

@Controller('health')
export class HealthController {
  constructor(
    private readonly health: HealthCheckService,
    private readonly db: TypeOrmHealthIndicator,
  ) {}

  @Get()
  @HealthCheck()
  check(): Promise<HealthCheckResult> {
    return this.health.check([
      () => this.db.pingCheck('database'),
    ]);
  }
}

@nestjs/terminus returns a 503 automatically when any indicator fails, so Vigilmon can detect failures without custom logic.


Step 3: Verify the Endpoint Locally

curl -s http://localhost:3000/health | jq .
# {
#   "status": "ok",
#   "version": "1.4.2",
#   "uptime": 38,
#   "checks": [{ "name": "database", "status": "ok", "responseTimeMs": 4 }]
# }

Disconnect your database and verify the endpoint returns 503.


Step 4: Create a Monitor in Vigilmon

  1. Log in to VigilmonMonitors → New Monitor
  2. Set the URL to https://api.yourdomain.com/health
  3. Set Check interval to 1 minute
  4. Under Expected response, ensure HTTP 200 is required
  5. Optionally add a Keyword check: "status":"ok" (this prevents Vigilmon from marking a 200 response with "status":"degraded" as healthy)
  6. Save and activate

Step 5: Receive Webhook Notifications in TypeScript

Handle Vigilmon webhook payloads with full type safety:

// src/webhooks/vigilmon.ts
import { Router, Request, Response } from 'express';

interface VigilmonMonitor {
  id: string;
  name: string;
  url: string;
}

interface VigilmonPayload {
  event: 'down' | 'up';
  monitor: VigilmonMonitor;
  checked_at: string;
  reason?: string;
}

const router = Router();

router.post('/webhooks/vigilmon', (req: Request<{}, {}, VigilmonPayload>, res: Response) => {
  const { event, monitor, checked_at, reason } = req.body;

  if (event === 'down') {
    console.error(`[Vigilmon] DOWN: ${monitor.name} (${monitor.url}) at ${checked_at}`, reason ?? '');
    // Trigger your incident workflow: PagerDuty, Slack, email, etc.
  } else {
    console.info(`[Vigilmon] RECOVERED: ${monitor.name} at ${checked_at}`);
  }

  res.sendStatus(200);
});

export default router;

Set up the webhook in Vigilmon under Alert Channels → Webhook and point it at https://api.yourdomain.com/webhooks/vigilmon.


Step 6: Add a Heartbeat for Worker Processes

If you run background workers (BullMQ, Agenda, custom consumers), they can silently stall while the HTTP server stays healthy. Add a heartbeat:

// src/workers/heartbeat.ts
import https from 'https';

const HEARTBEAT_URL = process.env.VIGILMON_HEARTBEAT_URL;

export function startHeartbeat(intervalMs = 60_000): NodeJS.Timeout {
  if (!HEARTBEAT_URL) return setInterval(() => {}, intervalMs);

  return setInterval(() => {
    https.get(HEARTBEAT_URL, (res) => {
      res.resume(); // discard body
    }).on('error', (err) => {
      console.warn('[Vigilmon] Heartbeat failed:', err.message);
    });
  }, intervalMs);
}

Create a Heartbeat monitor in Vigilmon with a 2-minute expected interval, then call startHeartbeat() from your worker entrypoint.


Step 7: Configure Alert Channels

Under Monitors → (your monitor) → Alert Channels, add:

| Channel | When | Purpose | |---------|------|---------| | Email | Immediately | Primary on-call notification | | Slack | Immediately | Team visibility | | Webhook | Immediately | Automated incident creation | | Email (escalation) | After 10 min | Secondary on-call if unacknowledged |

Enable Recovery notifications so you know when the incident resolves and can close the incident ticket.


Step 8: Type-Check Your Health Module in CI

Add a type-check step to your CI pipeline to catch type errors before they reach production:

# .github/workflows/ci.yml
- name: Type check
  run: npx tsc --noEmit

- name: Test health endpoint
  run: |
    npm run build
    node dist/server.js &
    sleep 3
    curl --fail http://localhost:3000/health
    kill %1

This verifies both compile-time correctness and runtime behaviour in CI.


Summary

| Step | What you did | |------|-------------| | 1 | Defined HealthResponse and HealthCheck TypeScript interfaces | | 2 | Implemented /health for Express, Fastify, or NestJS | | 3 | Tested locally with curl and a simulated DB failure | | 4 | Created an HTTP monitor in Vigilmon | | 5 | Added typed webhook handler for incident notifications | | 6 | Added heartbeat for background workers | | 7 | Configured multi-channel alert escalation |

Your TypeScript API now has typed, tested health endpoints and real-time uptime monitoring. Any regression in production availability triggers an alert before your users notice.


Further Reading

Monitor your app with Vigilmon

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

Start free →