Voltro + Neon

Connect Voltro to Neon serverless Postgres — the -pooler endpoint vs direct endpoint, sslmode=require, LISTEN/NOTIFY for reactivity, and using Neon branches for preview databases.

Neon is serverless Postgres, so it uses Voltro's postgres dialect — the reference dialect with the full reactive surface: LISTEN/NOTIFY CDC, advisory-lock cluster workflows, read replicas, and JSONB. The schema DSL, queries, and workflows are unchanged.

Neon's two things to get right: the pooled vs direct endpoint split (it affects reactivity exactly like Supabase's pooler does), and branching, which is a genuinely nice fit for Voltro preview deploys.

Connect

Neon gives you two host variants for the same database. The pooled host has -pooler in the subdomain:

# Pooled endpoint (PgBouncer, transaction mode) — for serverless /
# high connection churn. ⚠ Does NOT support LISTEN/NOTIFY.
DB_DIALECT=postgres
DB_URL=postgresql://[USER]:[PASSWORD]@[ENDPOINT]-pooler.[REGION].aws.neon.tech/[DB]?sslmode=require
# Direct endpoint (no -pooler) — session connection, supports
# LISTEN/NOTIFY. Use this for long-running processes that need the
# reactive engine, OR for the CDC listener specifically.
DB_DIALECT=postgres
DB_URL=postgresql://[USER]:[PASSWORD]@[ENDPOINT].[REGION].aws.neon.tech/[DB]?sslmode=require

sslmode=require is mandatory — Neon only accepts encrypted connections.

Enabling CDC

Voltro's change-data-capture is LISTEN/NOTIFY, on by default (CDC=1) — no extension, no flag. The single requirement on Neon is to point DB_URL (or at least the CDC listener) at the direct (non--pooler) endpoint, which is session-mode. The pooled -pooler endpoint is transaction-mode PgBouncer and breaks LISTEN/NOTIFY (below).

Voltro's reactive engine uses Postgres LISTEN/NOTIFY, which needs a long-lived, session-mode connection. Neon's pooled -pooler endpoint runs PgBouncer in transaction mode — each transaction gets a different backend, so a LISTEN registered on one is invisible to the next. Subscriptions then deliver their first snapshot but never update.

Decision rubric:

  • Long-running container / VM → use the direct (non-pooler) endpoint. Reactivity works out of the box. Watch your connection count against the compute's limit.
  • Serverless functions → use the pooled endpoint for app queries (PgBouncer is what makes high connection churn survivable), but the listener won't work there. Either run with CDC=0 (inline-emit, single-process, no cross-process fan-out) or split the listener onto a direct endpoint. A push-reactive Voltro app is happiest as a long-running process — prefer that over serverless if you want live subscriptions.

Autosuspend vs the long-lived listener

Neon's compute autosuspends when idle (scale-to-zero). The framework's CDC listener is a single long-lived connection that holds the LISTEN, so on a reactive Voltro app the listener is exactly what keeps the compute warm — useful if you want zero cold-start latency on writes, but it also means the compute never scales to zero while a Voltro process is connected (factor that into the cost model). When the compute is suspended (e.g. the app was down and Neon idled out), the listener connection is dropped on suspend; the framework reconnects on the next attempt and re-registers the LISTEN. Keep an eye on the connect/reconnect lines in voltro logs after an idle window — a missed reconnect shows up as subscriptions that snapshot but never update, same symptom as the pooler gotcha.

Branching → preview databases

Neon branches are copy-on-write database clones created in seconds. They map cleanly onto Voltro's auto-migrate boot flow: point a preview deploy at a fresh branch's connection string and voltro dev / voltro start auto-applies the schema on first boot.

# Per-preview-environment: a Neon branch URL drives an isolated DB.
DB_DIALECT=postgres
DB_URL=postgresql://[USER]:[PASSWORD]@[BRANCH-ENDPOINT].[REGION].aws.neon.tech/[DB]?sslmode=require

Each branch carries its own schema state, so previews never collide with production data. Tear the branch down when the preview is gone.

SSL + connection limits

  • SSL: always sslmode=require. Neon also supports the endpoint= SNI workaround for older drivers, but @effect/sql-pg (node-postgres) handles SNI natively — you don't need it.
  • Connection limits: the direct endpoint is bounded by the compute size; the pooled endpoint absorbs far more concurrent clients. Keep the CDC listener (one connection per process) on the direct endpoint and query bulk app traffic through the pooler when connection count is a concern.

Verify

After connecting, the boot log prints the resolved dialect:

[voltro:dev] sql dialect resolved: postgres — CDC: LISTEN/NOTIFY, RETURNING: native
[voltro:dev] read replicas: 0 configured (DB_REPLICA_URLS empty) — all queries → primary
[voltro:dev] workflow engine: cluster-sql, dialect=postgres

Confirm with voltro logs --tail 50. If subscriptions deliver an initial snapshot but never update, you're on the -pooler endpoint — move the listener to the direct endpoint.

See also

  • Postgres dialect — LISTEN/NOTIFY internals, advisory-lock workflows, read replicas, JSONB.
  • Vercel Postgres — Neon-backed; same connection model.
  • Supabase — the other major managed Postgres, same pooled-vs-direct split.