Fix: Neon Database Not Working — Connection Timeout, Branching Errors, or Serverless Driver Issues
Quick Answer
How to fix Neon Postgres issues — connection string setup, serverless HTTP driver vs TCP, database branching, connection pooling, Drizzle and Prisma integration, and cold start optimization.
The Problem
Connecting to Neon fails with a timeout:
import { Pool } from 'pg';
const pool = new Pool({ connectionString: process.env.DATABASE_URL });
const result = await pool.query('SELECT 1');
// Error: Connection terminated unexpectedly
// Or: connect ETIMEDOUTOr the serverless driver throws on Vercel Edge or Cloudflare Workers:
import { neon } from '@neondatabase/serverless';
const sql = neon(process.env.DATABASE_URL!);
const result = await sql`SELECT 1`;
// Error: fetch failed — or — Runtime error: TCP sockets are not availableOr a branched database has missing tables:
relation "users" does not existOr queries are slow on cold starts:
First query: 2.3s
Subsequent queries: 15msWhy This Happens
Neon is a serverless Postgres platform that separates compute from storage. Its architecture creates specific connection behaviors:
- Compute scales to zero — Neon suspends idle compute endpoints after a configurable period (default: 5 minutes). The first connection after suspension triggers a cold start that takes 300-500ms. TCP connections time out during this startup.
- Two driver options: HTTP and TCP — the
@neondatabase/serverlesspackage includes an HTTP-based driver (neon()) that works everywhere (edge runtimes, browsers) and a WebSocket-based TCP driver (Pool/Client) for long-lived connections. Using the wrong driver for your runtime causes failures. - Connection strings have two endpoints — Neon provides a pooled connection string (port 5432, goes through pgbouncer) and a direct connection string (port 5432 with
-poolersuffix). The pooled endpoint is required for serverless environments that create many short-lived connections. - Branches share the parent’s data at creation time — a branch is a copy-on-write snapshot. If you create a branch before running migrations, the branch has the old schema. Migrations must be applied to each branch independently.
Fix 1: Connect with the Serverless Driver
npm install @neondatabase/serverless// Option 1: HTTP driver — for serverless/edge (Vercel Edge, Cloudflare Workers)
// Single-shot queries over HTTP — no persistent connection
import { neon } from '@neondatabase/serverless';
const sql = neon(process.env.DATABASE_URL!);
// Tagged template literal syntax
const users = await sql`SELECT * FROM users WHERE role = ${'admin'}`;
// Parameters are automatically escaped — safe from SQL injection
// Insert
await sql`INSERT INTO users (name, email) VALUES (${'Alice'}, ${'[email protected]'})`;
// Transaction via HTTP
import { neon } from '@neondatabase/serverless';
const sql = neon(process.env.DATABASE_URL!, { fullResults: true });
const txn = await sql.transaction([
sql`INSERT INTO users (name, email) VALUES (${'Bob'}, ${'[email protected]'})`,
sql`INSERT INTO users (name, email) VALUES (${'Charlie'}, ${'[email protected]'})`,
sql`SELECT COUNT(*) FROM users`,
]);// Option 2: WebSocket driver — for Node.js, long-running servers
// Persistent connection via WebSocket (emulates TCP)
import { Pool, neonConfig } from '@neondatabase/serverless';
import ws from 'ws';
// Required: set WebSocket implementation for Node.js
neonConfig.webSocketConstructor = ws;
const pool = new Pool({ connectionString: process.env.DATABASE_URL });
const { rows } = await pool.query('SELECT * FROM users WHERE id = $1', [123]);
// Transaction
const client = await pool.connect();
try {
await client.query('BEGIN');
await client.query('INSERT INTO orders (user_id, total) VALUES ($1, $2)', [1, 99.99]);
await client.query('UPDATE users SET order_count = order_count + 1 WHERE id = $1', [1]);
await client.query('COMMIT');
} catch (e) {
await client.query('ROLLBACK');
throw e;
} finally {
client.release();
}
// Cleanup on shutdown
await pool.end();// Option 3: Standard pg driver — for traditional Node.js servers
// Uses Neon's pooled connection endpoint
import { Pool } from 'pg';
const pool = new Pool({
connectionString: process.env.DATABASE_URL,
ssl: { rejectUnauthorized: false }, // Required for Neon
max: 10,
idleTimeoutMillis: 30000,
});Fix 2: Connection String and Pooling
# Neon connection string format
# Direct (single compute):
# postgres://user:[email protected]/dbname?sslmode=require
# Pooled (through pgbouncer — recommended for serverless):
# postgres://user:[email protected]/dbname?sslmode=require
# ^^^^^^^^
# Note the -pooler suffix in hostname
# .env
DATABASE_URL="postgres://user:[email protected]/mydb?sslmode=require"
# Direct connection (for migrations — pgbouncer doesn't support some DDL)
DIRECT_DATABASE_URL="postgres://user:[email protected]/mydb?sslmode=require"When to use which endpoint:
| Use Case | Endpoint | Why |
|---|---|---|
| Serverless functions | Pooled (-pooler) | Short-lived connections, pgbouncer manages pool |
| Migrations (Drizzle, Prisma) | Direct (no -pooler) | DDL operations need direct connection |
| Long-running server | Either | Direct gives more control, pooled still works |
| Edge runtime (Workers, Edge) | HTTP driver (neon()) | No TCP sockets available |
Fix 3: Drizzle ORM Integration
npm install drizzle-orm @neondatabase/serverless
npm install -D drizzle-kit// src/db/index.ts — serverless setup
import { neon } from '@neondatabase/serverless';
import { drizzle } from 'drizzle-orm/neon-http';
import * as schema from './schema';
const sql = neon(process.env.DATABASE_URL!);
export const db = drizzle(sql, { schema });
// For WebSocket (Node.js server) setup
import { Pool, neonConfig } from '@neondatabase/serverless';
import { drizzle } from 'drizzle-orm/neon-serverless';
import ws from 'ws';
neonConfig.webSocketConstructor = ws;
const pool = new Pool({ connectionString: process.env.DATABASE_URL });
export const db = drizzle(pool, { schema });// drizzle.config.ts — use direct URL for migrations
import { defineConfig } from 'drizzle-kit';
export default defineConfig({
schema: './src/db/schema.ts',
out: './drizzle',
dialect: 'postgresql',
dbCredentials: {
url: process.env.DIRECT_DATABASE_URL!, // Direct, not pooled
},
});npx drizzle-kit generate
npx drizzle-kit push # Apply to Neon
npx drizzle-kit studio # Visual browserFix 4: Prisma Integration
npm install prisma @prisma/client @prisma/adapter-neon @neondatabase/serverless// prisma/schema.prisma
generator client {
provider = "prisma-client-js"
previewFeatures = ["driverAdapters"]
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
directUrl = env("DIRECT_DATABASE_URL") // For migrations
}
model User {
id Int @id @default(autoincrement())
email String @unique
name String
posts Post[]
createdAt DateTime @default(now())
}
model Post {
id Int @id @default(autoincrement())
title String
body String
author User @relation(fields: [authorId], references: [id])
authorId Int
}// src/db.ts — Prisma with Neon serverless adapter
import { Pool, neonConfig } from '@neondatabase/serverless';
import { PrismaNeon } from '@prisma/adapter-neon';
import { PrismaClient } from '@prisma/client';
import ws from 'ws';
neonConfig.webSocketConstructor = ws;
const pool = new Pool({ connectionString: process.env.DATABASE_URL });
const adapter = new PrismaNeon(pool);
export const prisma = new PrismaClient({ adapter });npx prisma generate
npx prisma db push # Sync schema to Neon
npx prisma migrate dev # Create and apply migrationFix 5: Database Branching
# Create a branch from main
npx neonctl branches create --name feature/auth --parent main
# List branches
npx neonctl branches list
# Get connection string for a branch
npx neonctl connection-string feature/auth
# Delete a branch
npx neonctl branches delete feature/auth// Use branching in CI/CD — each PR gets its own database branch
// .github/workflows/preview.yml
/*
steps:
- name: Create Neon branch
uses: neondatabase/create-branch-action@v4
with:
project_id: ${{ secrets.NEON_PROJECT_ID }}
branch_name: preview/pr-${{ github.event.number }}
api_key: ${{ secrets.NEON_API_KEY }}
- name: Run migrations
env:
DATABASE_URL: ${{ steps.create-branch.outputs.db_url }}
run: npx drizzle-kit push
- name: Run tests
env:
DATABASE_URL: ${{ steps.create-branch.outputs.db_url }}
run: npm test
*/Fix 6: Optimize Cold Starts
// 1. Use the HTTP driver for serverless — no connection overhead
import { neon } from '@neondatabase/serverless';
const sql = neon(process.env.DATABASE_URL!);
// Each query is a single HTTP request — no connection to establish
const users = await sql`SELECT * FROM users LIMIT 10`;
// 2. Enable connection caching on Neon dashboard
// Project Settings → Compute → Auto-suspend delay → set to 5-10 minutes
// Or disable auto-suspend for always-on (costs more)
// 3. Warm up with a simple query on function init
// In Vercel/AWS Lambda, code outside the handler runs once
const sql = neon(process.env.DATABASE_URL!);
const warmup = sql`SELECT 1`; // Starts during cold start
export default async function handler(req: Request) {
await warmup; // Ensure warmup completed
const users = await sql`SELECT * FROM users`;
return Response.json(users);
}
// 4. Use connection pooling for Node.js servers
import { Pool, neonConfig } from '@neondatabase/serverless';
import ws from 'ws';
neonConfig.webSocketConstructor = ws;
neonConfig.poolQueryViaFetch = true; // Use fetch for pool.query (faster)
const pool = new Pool({
connectionString: process.env.DATABASE_URL,
max: 5,
idleTimeoutMillis: 60000,
});Still Not Working?
“Connection terminated unexpectedly” with standard pg — Neon requires SSL. Add ?sslmode=require to your connection string or pass ssl: true in the pool config. Without SSL, the connection is rejected. For pg, use: new Pool({ connectionString: url, ssl: { rejectUnauthorized: false } }).
Migrations fail through the pooled endpoint — pgbouncer (the pooler) doesn’t support some DDL statements, prepared statements in transaction mode, or SET commands. Use the direct connection string (DIRECT_DATABASE_URL without -pooler) for migrations. The pooled endpoint is for application queries only.
Branch has no tables — branches are point-in-time snapshots of the parent. If you create a branch before running migrations on the parent, the branch has the old schema. Apply migrations to the branch separately, or create the branch after the parent’s schema is up to date.
Queries work but are unexpectedly slow — check if the compute endpoint suspended. The first query after suspension incurs a cold start (300-500ms). Increase the auto-suspend delay in the Neon dashboard or use the HTTP driver which handles cold starts more gracefully than TCP connections.
For related database issues, see Fix: Turso Not Working and Fix: Drizzle ORM Not Working.
Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.
Was this article helpful?
Related Articles
Fix: Turso Not Working — Connection Refused, Queries Returning Empty, or Embedded Replicas Not Syncing
How to fix Turso database issues — libsql client setup, connection URLs and auth tokens, embedded replicas for local-first apps, schema migrations, Drizzle ORM integration, and edge deployment.
Fix: Upstash Not Working — Redis Commands Failing, Rate Limiter Not Blocking, or QStash Messages Lost
How to fix Upstash issues — Redis REST client setup, rate limiting with @upstash/ratelimit, QStash message queues, Kafka topics, Vector search, and edge runtime integration.
Fix: Convex Not Working — Query Not Updating, Mutation Throwing Validation Error, or Action Timing Out
How to fix Convex backend issues — query/mutation/action patterns, schema validation, real-time reactivity, file storage, auth integration, and common TypeScript type errors.
Fix: Kysely Not Working — Type Errors on Queries, Migration Failing, or Generated Types Not Matching Schema
How to fix Kysely query builder issues — database interface definition, dialect setup, type-safe joins and subqueries, migration runner, kysely-codegen for generated types, and common TypeScript errors.