Initial commit
This commit is contained in:
539
skills/neon-drizzle/guides/troubleshooting.md
Normal file
539
skills/neon-drizzle/guides/troubleshooting.md
Normal file
@@ -0,0 +1,539 @@
|
||||
# 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
|
||||
Reference in New Issue
Block a user