Documentation

Database

Connection pool

Size the PostgreSQL connection pool of a VitNode app for a long-running server, serverless hosting or a transaction pooler such as PgBouncer or the Supabase pooler.

VitNode talks to Postgres through postgres.js, and the default config hands it a bare connection string:

src/vitnode.api.config.ts
dbProvider: drizzle({
  connection: POSTGRES_URL,
  relations: coreRelations,
}),

That means postgres.js defaults: up to 10 connections per process, idle connections kept forever (idle_timeout: 0), and automatic prepared statements (prepare: true). Perfectly fine on your laptop. In production you probably want to pick the numbers yourself.

Swap the string for an object with url plus any postgres.js option:

Long-running server

A Node server, a Docker container, a VPS. One process lives for days and handles plenty of requests at once, so give it room:

src/vitnode.api.config.ts
dbProvider: drizzle({
  connection: POSTGRES_URL, 
  connection: { url: POSTGRES_URL, max: 20, idle_timeout: 20 }, 
  relations: coreRelations,
}),
OptionDefaultWhat it does
max10The most connections this process will hold open. Queries beyond it wait their turn.
idle_timeout0Seconds before an unused connection is closed. 0 never closes it; 20 frees quiet periods.
preparetrueCreates named prepared statements automatically. Turn it off behind a transaction pooler.

Sizing rule of thumb: every process opens its own pool, and Postgres has a hard ceiling (SHOW max_connections;, often 100). So:

max ā‰ˆ (max_connections āˆ’ headroom) Ć· number of processes

Keep 10-20 connections of headroom for migrations, backups and you poking around in psql. With max_connections = 100, 4 instances and 20 spare, that is max: 20 each. Scale out to 8 instances and the same math says max: 10. Forget to redo it and the fifth instance greets you with too many connections.

Serverless

On Vercel, Netlify and friends every concurrent request may be its own process, so max Ɨ processes grows faster than you would like. Use your provider's pooled connection string, or keep each process to a single connection with max: 1. The Vercel guide has the full setup.

Behind a transaction pooler

Set prepare: false for transaction-mode poolers

The Supabase pooler on port 6543, PgBouncer with pool_mode = transaction and similar poolers hand you a different backend connection for every transaction. A prepared statement created on one backend does not exist on the next, so queries fail at random with prepared statement "..." does not exist. Turn automatic prepared statements off:

src/vitnode.api.config.ts
dbProvider: drizzle({
  connection: { url: POSTGRES_URL, prepare: false },
  relations: coreRelations,
}),

Direct connections and session-mode poolers keep prepare: true, the faster option. VitNode's own locks are transaction-scoped (pg_advisory_xact_lock), so they work behind either mode.