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 <noreply@anthropic.com>
This commit is contained in:
Chris Parsons 2026-04-06 17:15:18 -04:00 committed by GitHub
parent 4bcadb0749
commit 787b817fa7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 17 additions and 10 deletions

View file

@ -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=""

View file

@ -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 },
});

View file

@ -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.)
});

View file

@ -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;