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
- Connection Errors
- Adapter Issues
- Type Errors
- Query Errors
- Performance Issues
- Environment Issues
- Getting More Help
- Prevention Checklist
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:
- Check adapter configuration:
references/adapters.md - Review migration patterns:
references/migrations.md - Check query syntax:
references/query-patterns.md - Search Drizzle docs: https://orm.drizzle.team/docs
- 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