1. Documentation
  2. Databases
  3. PostgreSQL
ReadmenpmGitHub
  • Introduction
  • Quickstart
  • Configuration file
  • Monitors
  • Checks and scheduling
  • Notifications
  • Incidents and maintenance
  • Subscribers
  • Metrics and badges
  • Identity and appearance
  • Telemetry
  • The check run
  • Uptime and history
  • Automatic incidents
  • Notification delivery
  • Public subscriptions
  • StatusPage component
  • Themes
  • UI primitives
  • Colour mode
  • React Router
  • Next.js
  • Astro
  • SQLite
  • Cloudflare D1
  • PostgreSQL
  • Schema and migrations
  • Cloudflare Workers
  • Vercel
  • Self-hosting
  • API overview
  • Status endpoints
  • Incident and maintenance endpoints
  • Subscriber endpoints
  • Badges, feeds and metrics
  • Admin endpoints
  • CLI commands
  • Package exports
  • Security
  • Troubleshooting

PostgreSQL

Use node-postgres, Neon, Supabase or Drizzle with the same portable repository.

PostgreSQL is the database for Vercel and for any deployment that needs a managed, shared store. The adapter reuses the same portable SQL repository as D1, translated to the PostgreSQL dialect, so behaviour is identical across backends.

No driver is bundled. You pass a client, which keeps connection pooling and credentials in your hands.

heyo-status.database.ts
import { lazyStatusDatabase } from "@heyo-sh/heyo-status/db/lazy";import { postgres } from "@heyo-sh/heyo-status/deployments/vercel";import { drizzle } from "drizzle-orm/node-postgres";import { Pool } from "pg";export const database = lazyStatusDatabase(() => {  const connectionString = process.env.DATABASE_URL;  if (!connectionString) {    throw new Error("DATABASE_URL is required when using the PostgreSQL database overlay.");  }  return postgres(drizzle(new Pool({ connectionString })));});
Lazy, so a build without DATABASE_URL still works

The pool is created on the first query rather than on import, so a build step or a CLI invocation without the variable reports an actionable error instead of failing while the configuration module loads.

Accepted clients

postgres() takes anything with a compatible query surface:

ClientShape
pg Pool / Clientquery(text, values) => { rows }
Neon serverless driverthe same query shape
Supabase postgres clientthe same query shape
drizzle(pool)execute(query)
ts
import { postgres } from "@heyo-sh/heyo-status/db/postgres";import { Pool } from "pg";const database = postgres(new Pool({ connectionString: process.env.DATABASE_URL }));

Both import paths are the same implementation: @heyo-sh/heyo-status/db/postgres and @heyo-sh/heyo-status/deployments/vercel.

Migrations

bash
heyo-status db migrate

Through the lazy wrapper this calls PostgresStatusDatabase.migrate(), which applies the bundled migrations and records them in __heyo_status_migrations. Run it:

  • once per environment before the first deploy, and
  • after upgrading the package, as part of your release job.
ts
// A one-off deployment migration job.import { database } from "./heyo-status.database";await database.migrate();

Request handlers never migrate automatically, so a cold start cannot race schema changes.

Options

ts
postgres(client);postgres(client, { migrate: true });

migrate: true applies migrations on construction — appropriate for local and test databases, not for a shared production one.

Connection pooling

Serverless functions and pooled PostgreSQL need care:

  • On Vercel, prefer a pooled connection string (PgBouncer, Neon pooler, Supabase pooler). A direct connection per invocation exhausts the server.
  • Keep the pool small; a status page issues a handful of queries per request.
  • A status page is read-heavy, and cache.snapshotTtlMs removes most repeated reads inside one instance.

Operational notes

  • Timestamps are stored as epoch milliseconds, exactly as in SQLite and D1, so history is portable between backends.
  • A unique index on the subscriber destination is what makes concurrent subscription requests resolve to one row.
  • heyo-status check run and db seed work against PostgreSQL from a developer machine as long as DATABASE_URL is reachable.

Related

  • Deploying to Vercel
  • Schema and migrations
  • Troubleshooting
Cloudflare D1< PreviousSchema and migrationsNext >

Powered by heyo

On this page

Accepted clientsMigrationsOptionsConnection poolingOperational notes