From 787b817fa71c0a11d59fec69687be2200847b379 Mon Sep 17 00:00:00 2001 From: Chris Parsons <438676+chrisparsons83@users.noreply.github.com> Date: Mon, 6 Apr 2026 17:15:18 -0400 Subject: [PATCH] Add PgBouncer connection pooling support for Vultr managed PostgreSQL (#271) Disables prepared statements when PGBOUNCER=true (required for PgBouncer transaction mode). Adds DATABASE_DIRECT_URL so migrations always use a direct connection, bypassing the pool. Co-authored-by: Claude Sonnet 4.6 --- .env.example | 7 +++++++ drizzle.config.ts | 9 +++------ scripts/migrate.mjs | 7 ++++--- server/db.ts | 4 +++- 4 files changed, 17 insertions(+), 10 deletions(-) diff --git a/.env.example b/.env.example index 3afec4a..51e6cb5 100644 --- a/.env.example +++ b/.env.example @@ -1,5 +1,12 @@ NODE_ENV="development" DATABASE_URL="" +# PgBouncer support (Vultr managed PostgreSQL) +# Set PGBOUNCER=true when DATABASE_URL points to a PgBouncer endpoint (port 6432). +# This disables prepared statements, required for PgBouncer transaction mode. +# DATABASE_DIRECT_URL is used by migrations; it must be a direct connection (port 5432). +# Leave both unset for local development with plain PostgreSQL. +PGBOUNCER= +DATABASE_DIRECT_URL= CLERK_SECRET_KEY="" CLERK_PUBLISHABLE_KEY="" CLERK_WEBHOOK_SECRET="" diff --git a/drizzle.config.ts b/drizzle.config.ts index 983a826..422ca07 100644 --- a/drizzle.config.ts +++ b/drizzle.config.ts @@ -1,14 +1,11 @@ import { defineConfig } from "drizzle-kit"; -if (!process.env.DATABASE_URL) { - throw new Error("DATABASE_URL is required"); -} +const url = process.env.DATABASE_DIRECT_URL || process.env.DATABASE_URL; +if (!url) throw new Error("DATABASE_URL (or DATABASE_DIRECT_URL) is required"); export default defineConfig({ out: "./drizzle", schema: "./database/schema.ts", dialect: "postgresql", - dbCredentials: { - url: process.env.DATABASE_URL, - }, + dbCredentials: { url }, }); diff --git a/scripts/migrate.mjs b/scripts/migrate.mjs index 09e89e6..d31e7c9 100644 --- a/scripts/migrate.mjs +++ b/scripts/migrate.mjs @@ -7,13 +7,14 @@ import path from "path"; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); -if (!process.env.DATABASE_URL) { - console.error("ERROR: DATABASE_URL is required"); +const migrationUrl = process.env.DATABASE_DIRECT_URL || process.env.DATABASE_URL; +if (!migrationUrl) { + console.error("ERROR: DATABASE_URL (or DATABASE_DIRECT_URL) is required"); process.exit(1); } console.log("Running database migrations..."); -const client = postgres(process.env.DATABASE_URL, { +const client = postgres(migrationUrl, { max: 1, onnotice: () => {}, // suppress NOTICE messages (schema/table already exists, etc.) }); diff --git a/server/db.ts b/server/db.ts index 911f809..dc4c194 100644 --- a/server/db.ts +++ b/server/db.ts @@ -14,7 +14,9 @@ declare global { function getDb() { if (!globalThis.__pgDb) { if (!process.env.DATABASE_URL) throw new Error("DATABASE_URL is required"); - globalThis.__pgClient ??= postgres(process.env.DATABASE_URL); + globalThis.__pgClient ??= postgres(process.env.DATABASE_URL, { + prepare: process.env.PGBOUNCER !== "true", + }); globalThis.__pgDb = drizzle(globalThis.__pgClient, { schema }); } return globalThis.__pgDb;