tutorial

How to Monitor Drizzle ORM with Vigilmon

Keep your Drizzle ORM–powered database layer healthy in production — health checks, query latency alerts, migration safety, and heartbeat monitoring for background jobs.

How to Monitor Drizzle ORM with Vigilmon

Your Drizzle ORM app shipped clean. The queries are typed, migrations are versioned, and the bundle is tiny. Then one Saturday morning your database connection pool silently exhausts and users start seeing blank screens — and you found out from a tweet.

This tutorial covers what happens after you deploy. By the end you'll have:

  • A /health endpoint that verifies your Drizzle database connection
  • HTTP uptime monitoring with Vigilmon
  • Alerts when your database goes unreachable or queries time out
  • Heartbeat monitoring for drizzle-kit migration jobs

Setup takes about 20 minutes and is free on Vigilmon's starter plan.


Why Monitor a Drizzle ORM Application

Drizzle's zero-runtime-overhead design means problems surface as database errors, not framework errors. Common failure modes:

  • Connection pool exhaustion — too many concurrent queries, pool fills, new requests queue indefinitely
  • Silent migration failuredrizzle-kit push exits non-zero in CI but the app keeps running on the old schema
  • Edge runtime cold start — Cloudflare Workers or Vercel Edge Functions reconnect on every invocation; connection errors at cold start appear as 500s, not database errors
  • Query timeout — a slow query holds a pool slot; median latency looks fine but p99 is 10 seconds

None of these turn off your server process. HTTP monitoring won't catch them unless your health endpoint actually runs a query.


Key Metrics to Watch

| Metric | What it means | |--------|---------------| | Health endpoint status | Database reachable, pool has free slots | | Query round-trip latency | Proxy lag, pool wait, index health | | Migration job success | Schema stays in sync with code | | Connection pool size | Approaching max → alert before exhaustion | | Error rate on DB routes | Unhandled query errors reaching users |


Step 1: Add a Health Endpoint with a Real Query

A health endpoint that only returns { status: "ok" } without touching the database is a false signal. Run a lightweight query that exercises the connection pool.

With Hono or Express

// src/health.ts
import { db } from './db';
import { sql } from 'drizzle-orm';

export async function checkDatabaseHealth(): Promise<{
  status: 'ok' | 'error';
  latencyMs: number;
  error?: string;
}> {
  const start = performance.now();
  try {
    await db.execute(sql`SELECT 1`);
    return { status: 'ok', latencyMs: Math.round(performance.now() - start) };
  } catch (err) {
    return {
      status: 'error',
      latencyMs: Math.round(performance.now() - start),
      error: err instanceof Error ? err.message : String(err),
    };
  }
}

Wire it to an HTTP route:

// Express
app.get('/health', async (req, res) => {
  const db = await checkDatabaseHealth();
  const status = db.status === 'ok' ? 200 : 503;
  res.status(status).json({ database: db });
});

// Hono
app.get('/health', async (c) => {
  const db = await checkDatabaseHealth();
  const status = db.status === 'ok' ? 200 : 503;
  return c.json({ database: db }, status);
});

With Next.js App Router

// app/api/health/route.ts
import { NextResponse } from 'next/server';
import { checkDatabaseHealth } from '@/lib/health';

export async function GET() {
  const db = await checkDatabaseHealth();
  const status = db.status === 'ok' ? 200 : 503;
  return NextResponse.json({ database: db }, { status });
}

Test locally:

curl http://localhost:3000/health
# {"database":{"status":"ok","latencyMs":4}}

When the database is unreachable:

{ "database": { "status": "error", "latencyMs": 5001, "error": "connect ECONNREFUSED" } }

Vigilmon reacts to the 503 status code, so you get alerted without any custom webhook logic.


Step 2: Monitor the Health Endpoint with Vigilmon

  1. Sign up at vigilmon.online (free, no card required)
  2. Click New Monitor → HTTP
  3. Enter https://api.yourdomain.com/health
  4. Set check interval: 1 minute (paid) or 5 minutes (free)
  5. Under Expected status code, set 200
  6. Save

Add a second monitor for your main API surface so you know whether a failure is database-specific or wider:

https://api.yourdomain.com/health     → database check
https://api.yourdomain.com/           → root alive

If the root is up but /health returns 503, the problem is the database — not your server.


Step 3: Alert on Query Latency

The health endpoint returns latencyMs in its JSON body. You can monitor this with Vigilmon's keyword check:

  1. Edit your health monitor → Advanced options
  2. Enable Response body contains: set a pattern that would be absent if latency is normal (or use a separate canary endpoint that returns "slow":true when latency exceeds your threshold)

Alternatively, add a dedicated latency canary:

app.get('/health/latency', async (req, res) => {
  const result = await checkDatabaseHealth();
  if (result.latencyMs > 500) {
    return res.status(503).json({ slow: true, latencyMs: result.latencyMs });
  }
  res.json({ slow: false, latencyMs: result.latencyMs });
});

Create a Vigilmon monitor for /health/latency expecting a 200. When the database slows down, you're alerted — often before users even notice.


Step 4: Heartbeat Monitoring for Migration Jobs

drizzle-kit migrate is typically run in CI or as a one-off deployment step. If it fails silently, your app runs on a stale schema and you get cryptic column-not-found errors hours later.

The heartbeat pattern: ping a unique URL at the end of every successful migration. If Vigilmon doesn't see a ping within your expected window, it alerts you.

Wrap your migration script:

// scripts/migrate.ts
import { drizzle } from 'drizzle-orm/postgres-js';
import { migrate } from 'drizzle-orm/postgres-js/migrator';
import postgres from 'postgres';

const HEARTBEAT_URL = process.env.VIGILMON_MIGRATION_HEARTBEAT;

async function runMigrations() {
  const sql = postgres(process.env.DATABASE_URL!, { max: 1 });
  const db = drizzle(sql);

  console.log('Running migrations...');
  await migrate(db, { migrationsFolder: './drizzle' });
  console.log('Migrations complete');

  await sql.end();

  if (HEARTBEAT_URL) {
    await fetch(HEARTBEAT_URL);
    console.log('Heartbeat sent');
  }
}

runMigrations().catch((err) => {
  console.error('Migration failed:', err);
  process.exit(1);
  // No heartbeat sent → Vigilmon alerts on missed ping
});

In Vigilmon:

  1. Click New Monitor → Heartbeat
  2. Set expected interval to match your deployment cadence (e.g., 24 hours for daily deploys)
  3. Copy the ping URL → set as VIGILMON_MIGRATION_HEARTBEAT in your CI environment
  4. Save

Now if migrations stop running (CI skipped, job failed, deployment paused), you'll know before users hit schema mismatch errors.


Step 5: Slack Alerts

Go to Notifications → New Channel → Slack in Vigilmon, paste your incoming webhook URL, and enable the channel on your monitors.

When your database goes down, you'll see:

🔴 DOWN: api.yourdomain.com/health
Status: 503 Service Unavailable
Region: EU-West
3 minutes ago

And on recovery:

✅ RECOVERED: api.yourdomain.com/health
Downtime: 12 minutes

Step 6: Status Page

In Vigilmon, go to Status Pages → New Status Page, add your health and root monitors, and publish.

Link it from your error responses:

app.use((err, req, res, next) => {
  res.status(503).json({
    error: 'Service temporarily unavailable',
    status: 'https://status.yourdomain.com',
  });
});

What You've Built

| What | How | |------|-----| | Database health check | Drizzle SELECT 1 with latency measurement | | HTTP uptime monitoring | Vigilmon HTTP monitor → /health | | Latency alerting | Canary endpoint returning 503 on slow queries | | Migration job monitoring | Heartbeat ping after successful migrate() | | Slack alerts | Vigilmon Slack notification channel | | Status page | Vigilmon public status page |

Your Drizzle ORM layer is now observable. You'll know about database problems before your users report them.


Next Steps

  • Add a pool utilization check to your health endpoint using your driver's pool stats
  • Monitor Drizzle Studio's port if you expose it internally — unexpected 404s may signal a stale deployment
  • Add separate heartbeat monitors for any background data sync jobs that use Drizzle

Get started 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 →