DEV Community

Vigilmon
Vigilmon

Posted on

Uptime Monitoring for PlanetScale-Powered Apps (Free, Multi-Region)

Uptime Monitoring for PlanetScale-Powered Apps (Free, Multi-Region)

PlanetScale brings branching, non-blocking schema changes, and serverless connection pooling to MySQL. It removes a lot of operational pain — but it doesn't alert you when your app can't reach the database.

This guide shows you how to add a health endpoint to a PlanetScale-backed application, what to monitor, and how to set up free external uptime monitoring in about 20 minutes.


The failure modes that bite PlanetScale developers

Connection limit exhaustion — PlanetScale's serverless driver pools connections per branch. Serverless functions that open connections without closing them hit the per-branch limit fast. When that happens, new queries hang until timeout. Your app stays up, responses just take 30 seconds instead of 30ms.

Branch or deploy request issues — If you're running automated deploy requests as part of CI, a failed migration leaves your schema mid-flight. Queries against the affected table start returning schema errors. Nothing in your process monitor catches this.

Credential rotation — PlanetScale database passwords are per-branch. After rotation, if a stale password is in your environment, queries fail silently unless your health check verifies connectivity on every probe.


Step 1: Create a health check endpoint

Here's a Node.js example using the PlanetScale serverless driver:

// src/health.ts
import { connect } from '@planetscale/database'

const config = {
  host: process.env.DATABASE_HOST,
  username: process.env.DATABASE_USERNAME,
  password: process.env.DATABASE_PASSWORD,
}

interface HealthResult {
  status: 'ok' | 'error'
  latencyMs?: number
  error?: string
}

export async function checkDatabase(): Promise<HealthResult> {
  const conn = connect(config)
  const start = Date.now()
  try {
    const results = await conn.execute('SELECT 1 as probe')
    const latencyMs = Date.now() - start
    if (results.rows.length === 0) {
      return { status: 'error', error: 'Empty result from probe query' }
    }
    return { status: 'ok', latencyMs }
  } catch (err: any) {
    return { status: 'error', error: err.message }
  }
}

export async function healthHandler(req: any, res: any) {
  const db = await checkDatabase()
  const allOk = db.status === 'ok'

  res.status(allOk ? 200 : 503).json({
    status: allOk ? 'ok' : 'degraded',
    checks: { database: db },
    branch: process.env.DATABASE_HOST?.split('.')[0] ?? 'unknown',
    timestamp: new Date().toISOString(),
  })
}
Enter fullscreen mode Exit fullscreen mode

In Express:

import express from 'express'
import { healthHandler } from './health'

const app = express()
app.get('/health', healthHandler)
app.listen(3000)
Enter fullscreen mode Exit fullscreen mode

In Next.js (App Router):

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

export async function GET() {
  const db = await checkDatabase()
  const allOk = db.status === 'ok'

  return NextResponse.json(
    { status: allOk ? 'ok' : 'degraded', checks: { database: db } },
    { status: allOk ? 200 : 503 }
  )
}
Enter fullscreen mode Exit fullscreen mode

Step 2: Track connection pool depth

For long-running servers (not serverless), add a connection pool check:

import { createPool } from '@vercel/postgres' // or mysql2 pooling

const pool = createPool({
  connectionString: process.env.DATABASE_URL,
  max: 10, // match your PlanetScale plan limit
})

async function checkPool(): Promise<HealthResult> {
  const start = Date.now()
  let client
  try {
    client = await pool.connect()
    await client.query('SELECT 1')
    return { status: 'ok', latencyMs: Date.now() - start }
  } catch (err: any) {
    return { status: 'error', error: err.message }
  } finally {
    client?.release()
  }
}
Enter fullscreen mode Exit fullscreen mode

Add this metric to your health response and graph it over time — a rising latency trend tells you you're approaching connection exhaustion before queries start failing.


Step 3: Monitor the main branch vs production branches separately

PlanetScale's branching model means you might run separate monitors per branch:

Branch URL Monitor interval
main (production) https://api.example.com/health 60s
shadow (canary) https://shadow.api.example.com/health 5m

Set the production monitor to a tighter interval and stricter alert thresholds.


Step 4: Set up external monitoring

  1. Visit vigilmon.online and create a free account.
  2. Add an HTTP(S) monitor for https://your-app.com/health.
  3. Set interval to 60 seconds.
  4. Set expected status: 200.
  5. Add two or more monitoring regions (e.g. EU West, US East).
  6. Configure an alert: email or Slack.

Within a minute your first probes run. If your PlanetScale credentials expire or connection pools exhaust, you'll know in under two minutes.


Step 5: Alert on latency, not just downtime

PlanetScale's serverless connections sometimes slow down before they fail outright. Set a latency threshold alert in your monitoring:

  • Warning at 500ms
  • Critical at 2000ms

That gives you a heads-up to check the PlanetScale insights dashboard before users start bouncing.


Recap

  1. Add a /health endpoint that runs SELECT 1 against PlanetScale and returns structured JSON.
  2. Include the branch name in your health response so alerts tell you which branch is failing.
  3. Monitor production and canary branches on separate intervals.
  4. Set up a free external monitor at vigilmon.online — multi-region, 60-second probes, no credit card.
  5. Alert on latency thresholds before connection exhaustion becomes an outage.

PlanetScale makes your database resilient to schema changes. Uptime monitoring makes your application resilient to the things PlanetScale can't control.

Top comments (0)