540 lines
11 KiB
Markdown
540 lines
11 KiB
Markdown
# 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](#migration-errors)
|
|
- [Connection Errors](#connection-errors)
|
|
- [Adapter Issues](#adapter-issues)
|
|
- [Type Errors](#type-errors)
|
|
- [Query Errors](#query-errors)
|
|
- [Performance Issues](#performance-issues)
|
|
- [Environment Issues](#environment-issues)
|
|
- [Getting More Help](#getting-more-help)
|
|
- [Prevention Checklist](#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**
|
|
```bash
|
|
export DATABASE_URL="$(grep DATABASE_URL .env.local | cut -d '=' -f2)" && \
|
|
[package-manager] drizzle-kit migrate
|
|
```
|
|
|
|
**Option 2: Update drizzle.config.ts**
|
|
```typescript
|
|
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**
|
|
```typescript
|
|
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:**
|
|
```bash
|
|
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)**
|
|
```bash
|
|
rm src/db/migrations/[latest-migration-file].sql
|
|
[package-manager] drizzle-kit generate
|
|
```
|
|
|
|
**Option 2: Drop and recreate table (dev only, DATA LOSS)**
|
|
```bash
|
|
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:
|
|
```sql
|
|
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:
|
|
```bash
|
|
cat src/db/migrations/meta/_journal.json
|
|
```
|
|
|
|
Remove duplicate entry or regenerate:
|
|
```bash
|
|
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:
|
|
```bash
|
|
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:
|
|
```bash
|
|
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:
|
|
```typescript
|
|
import { neonConfig } from '@neondatabase/serverless';
|
|
import ws from 'ws';
|
|
|
|
neonConfig.webSocketConstructor = ws;
|
|
```
|
|
|
|
Install ws if missing:
|
|
```bash
|
|
[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:
|
|
```typescript
|
|
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:**
|
|
```typescript
|
|
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:**
|
|
```typescript
|
|
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:**
|
|
```typescript
|
|
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:**
|
|
```typescript
|
|
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):
|
|
```typescript
|
|
const user = await db.insert(users).values({
|
|
email: 'test@example.com',
|
|
});
|
|
```
|
|
|
|
### Error: "Property 'xyz' does not exist"
|
|
|
|
**Symptom:**
|
|
```typescript
|
|
const user = await db.select().from(users);
|
|
console.log(user[0].nonExistentField); // Error
|
|
```
|
|
|
|
**Cause:** Column not defined in schema.
|
|
|
|
**Solution:**
|
|
|
|
Add column to schema:
|
|
```typescript
|
|
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:
|
|
```bash
|
|
[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:
|
|
```bash
|
|
[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:**
|
|
```typescript
|
|
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**
|
|
```typescript
|
|
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:
|
|
```typescript
|
|
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:
|
|
```typescript
|
|
const postsWithAuthors = await db.query.posts.findMany({
|
|
with: {
|
|
author: true,
|
|
},
|
|
});
|
|
```
|
|
|
|
**3. Selecting too much data**
|
|
|
|
Select only needed columns:
|
|
```typescript
|
|
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:
|
|
```typescript
|
|
import { Pool } from '@neondatabase/serverless';
|
|
|
|
const pool = new Pool({
|
|
connectionString: process.env.DATABASE_URL!,
|
|
max: 10,
|
|
connectionTimeoutMillis: 5000,
|
|
idleTimeoutMillis: 30000,
|
|
});
|
|
```
|
|
|
|
**3. Add query timeout:**
|
|
```typescript
|
|
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:**
|
|
```bash
|
|
ls .env .env.local
|
|
```
|
|
|
|
**2. Verify var is set:**
|
|
```bash
|
|
grep DATABASE_URL .env.local
|
|
```
|
|
|
|
**3. Load env vars:**
|
|
```typescript
|
|
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:**
|
|
```bash
|
|
# 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
|