Files
gh-neondatabase-labs-ai-rul…/skills/neon-drizzle/guides/troubleshooting.md
2025-11-30 08:43:11 +08:00

11 KiB

Troubleshooting Guide

Reference Guide: This is organized by error type and solution, not sequential phases. Jump directly to the error you're experiencing for quick resolution.

Common issues and solutions for Drizzle ORM with Neon.

Table of Contents


Migration Errors

Error: "url: undefined"

Symptom:

Error: url is undefined in dbCredentials

Cause: Environment variables not loaded during migration.

Solutions:

Option 1: Explicit env loading

export DATABASE_URL="$(grep DATABASE_URL .env.local | cut -d '=' -f2)" && \
[package-manager] drizzle-kit migrate

Option 2: Update drizzle.config.ts

import { defineConfig } from 'drizzle-kit';
import { config } from 'dotenv';

config({ path: '.env.local' });

export default defineConfig({
  schema: './src/db/schema.ts',
  out: './src/db/migrations',
  dialect: 'postgresql',
  dbCredentials: {
    url: process.env.DATABASE_URL!,
  },
});

Option 3: Use programmatic migration

import { migrate } from 'drizzle-orm/neon-http/migrator';
import { db } from './src/db';
import { config } from 'dotenv';

config({ path: '.env.local' });

await migrate(db, { migrationsFolder: './src/db/migrations' });

Error: "Cannot find migrations folder"

Symptom:

Error: ENOENT: no such file or directory, scandir './src/db/migrations'

Cause: Migrations folder doesn't exist yet.

Solution:

mkdir -p src/db/migrations
[package-manager] drizzle-kit generate

Error: "Column already exists"

Symptom:

Error: column "name" of relation "users" already exists

Cause: Trying to add a column that already exists in the database.

Solutions:

Option 1: Skip migration (dev only)

rm src/db/migrations/[latest-migration-file].sql
[package-manager] drizzle-kit generate

Option 2: Drop and recreate table (dev only, DATA LOSS)

psql $DATABASE_URL -c "DROP TABLE users CASCADE;"
[package-manager] drizzle-kit migrate

Option 3: Manual migration (production) Edit the migration file to check if column exists:

ALTER TABLE users
  ADD COLUMN IF NOT EXISTS name VARCHAR(255);

Error: "Migration already applied"

Symptom:

Error: migration has already been applied

Cause: Drizzle tracks applied migrations. Trying to reapply.

Solution:

Check migration journal:

cat src/db/migrations/meta/_journal.json

Remove duplicate entry or regenerate:

rm -rf src/db/migrations
mkdir src/db/migrations
[package-manager] drizzle-kit generate

Warning: Only do this in development!

Connection Errors

Error: "Connection refused"

Symptom:

Error: connect ECONNREFUSED

Causes and Solutions:

1. Wrong DATABASE_URL format

Check format:

echo $DATABASE_URL

Should be:

postgresql://user:password@host.neon.tech/dbname?sslmode=require

2. Missing sslmode

Add to DATABASE_URL:

?sslmode=require

3. Firewall/network issue

Test connectivity:

psql $DATABASE_URL -c "SELECT 1"

Error: "WebSocket connection failed"

Symptom:

Error: WebSocket connection to 'wss://...' failed

Cause: Missing WebSocket constructor in Node.js.

Solution:

Add to your connection file:

import { neonConfig } from '@neondatabase/serverless';
import ws from 'ws';

neonConfig.webSocketConstructor = ws;

Install ws if missing:

[package-manager] add ws
[package-manager] add -D @types/ws

Error: "Too many connections"

Symptom:

Error: sorry, too many clients already

Cause: Connection pool exhausted.

Solutions:

For HTTP adapter: This shouldn't happen (stateless).

For WebSocket adapter: Implement connection pooling:

import { Pool } from '@neondatabase/serverless';

const pool = new Pool({
  connectionString: process.env.DATABASE_URL!,
  max: 10, // Limit connections
});

export const db = drizzle(pool);

Close connections properly:

process.on('SIGTERM', async () => {
  await pool.end();
  process.exit(0);
});

Adapter Issues

Wrong Adapter for Environment

Symptom: App works locally but fails in production (or vice versa).

Cause: Using wrong adapter for environment.

Solutions:

See references/adapters.md for decision guide.

Quick reference:

  • Vercel/Cloudflare/Edge → HTTP adapter
  • Node.js/Express/Long-lived → WebSocket adapter

HTTP adapter:

import { drizzle } from 'drizzle-orm/neon-http';
import { neon } from '@neondatabase/serverless';

const sql = neon(process.env.DATABASE_URL!);
export const db = drizzle(sql);

WebSocket adapter:

import { drizzle } from 'drizzle-orm/neon-serverless';
import { Pool, neonConfig } from '@neondatabase/serverless';
import ws from 'ws';

neonConfig.webSocketConstructor = ws;
const pool = new Pool({ connectionString: process.env.DATABASE_URL! });
export const db = drizzle(pool);

Type Errors

Error: "Type 'number' is not assignable to type 'string'"

Symptom:

const user = await db.insert(users).values({
  id: 1, // Error here
  email: 'test@example.com',
});

Cause: Trying to manually set auto-increment ID.

Solution:

Remove id from insert (it's auto-generated):

const user = await db.insert(users).values({
  email: 'test@example.com',
});

Error: "Property 'xyz' does not exist"

Symptom:

const user = await db.select().from(users);
console.log(user[0].nonExistentField); // Error

Cause: Column not defined in schema.

Solution:

Add column to schema:

export const users = pgTable('users', {
  id: serial('id').primaryKey(),
  nonExistentField: text('non_existent_field'),
});

Then regenerate and apply migration.

Query Errors

Error: "relation does not exist"

Symptom:

Error: relation "users" does not exist

Cause: Table not created in database yet.

Solution:

Run migrations:

[package-manager] drizzle-kit generate
export DATABASE_URL="$(grep DATABASE_URL .env.local | cut -d '=' -f2)" && \
[package-manager] drizzle-kit migrate

Error: "column does not exist"

Symptom:

Error: column "email" does not exist

Causes:

1. Schema out of sync with database

Regenerate and apply migrations:

[package-manager] drizzle-kit generate
[package-manager] drizzle-kit migrate

2. Wrong table name in query

Check schema definition vs query.

3. Case sensitivity

PostgreSQL is case-sensitive. Ensure column names match exactly.

Error: "Cannot perform transactions with HTTP adapter"

Symptom:

await db.transaction(async (tx) => {
  // Error: transactions not supported
});

Cause: HTTP adapter doesn't support transactions.

Solutions:

Option 1: Switch to WebSocket adapter (if environment allows)

See references/adapters.md.

Option 2: Use batch operations

await db.batch([
  db.insert(users).values({ email: 'test1@example.com' }),
  db.insert(posts).values({ title: 'Test' }),
]);

Option 3: Implement application-level rollback

Not ideal, but possible for simple cases.

Performance Issues

Slow Queries

Symptoms: Queries taking seconds instead of milliseconds.

Diagnose:

1. Missing indexes

Check if foreign keys have indexes:

export const posts = pgTable('posts', {
  id: serial('id').primaryKey(),
  authorId: serial('author_id').notNull(),
}, (table) => ({
  authorIdIdx: index('posts_author_id_idx').on(table.authorId), // ADD THIS
}));

2. N+1 queries

Use relations instead of multiple queries:

const postsWithAuthors = await db.query.posts.findMany({
  with: {
    author: true,
  },
});

3. Selecting too much data

Select only needed columns:

const users = await db.select({
  id: users.id,
  email: users.email,
}).from(users);

Connection Timeout

Symptom: Queries timeout in production.

Solutions:

1. For Vercel: Ensure using HTTP adapter (see references/adapters.md)

2. For Node.js: Implement connection pooling with retry:

import { Pool } from '@neondatabase/serverless';

const pool = new Pool({
  connectionString: process.env.DATABASE_URL!,
  max: 10,
  connectionTimeoutMillis: 5000,
  idleTimeoutMillis: 30000,
});

3. Add query timeout:

const result = await Promise.race([
  db.select().from(users),
  new Promise((_, reject) =>
    setTimeout(() => reject(new Error('Query timeout')), 5000)
  ),
]);

Environment Issues

Error: "DATABASE_URL is undefined"

Symptom: App can't find DATABASE_URL.

Solutions:

1. Check env file exists:

ls .env .env.local

2. Verify var is set:

grep DATABASE_URL .env.local

3. Load env vars:

import { config } from 'dotenv';
config({ path: '.env.local' });

4. For Next.js: Use NEXT_PUBLIC_ prefix if accessing client-side (NOT recommended for DATABASE_URL):

# Don't do this - security risk
NEXT_PUBLIC_DATABASE_URL="..."

# Do this - server-only
DATABASE_URL="..."

Error: "Invalid connection string"

Symptom:

Error: invalid connection string

Cause: Malformed DATABASE_URL.

Check format:

postgresql://USER:PASSWORD@HOST:PORT/DATABASE?sslmode=require

Common mistakes:

  • Missing postgresql:// prefix
  • Special characters in password not URL-encoded
  • Missing ?sslmode=require

Fix special characters:

# If password is "p@ss&word!"
# Encode to: p%40ss%26word%21

Getting More Help

If your issue isn't listed here:

  1. Check adapter configuration: references/adapters.md
  2. Review migration patterns: references/migrations.md
  3. Check query syntax: references/query-patterns.md
  4. Search Drizzle docs: https://orm.drizzle.team/docs
  5. Check Neon docs: https://neon.com/docs

Prevention Checklist

Before deploying:

  • Environment variables properly loaded
  • Correct adapter for environment
  • Migrations applied successfully
  • Indexes on foreign keys
  • Connection pooling configured (if Node.js)
  • Error handling for database operations
  • .env files in .gitignore
  • Test queries work in production environment