Initial commit
This commit is contained in:
398
skills/neon-drizzle/guides/existing-project.md
Normal file
398
skills/neon-drizzle/guides/existing-project.md
Normal file
@@ -0,0 +1,398 @@
|
||||
# Existing Project Integration
|
||||
|
||||
> **Complete Walkthrough**: This is a self-contained, step-by-step guide with its own numbered phases (Phase 1-8).
|
||||
> Follow each phase in order to safely add Drizzle to your existing application.
|
||||
|
||||
Guide for adding Drizzle ORM to an existing application with Neon.
|
||||
|
||||
### Important:
|
||||
- Remember to run the neon-plugin:add-neon-docs skill with the parameter SKILL_NAME="neon-drizzle" after completing the guide.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Workflow Checklist](#workflow-checklist)
|
||||
- [Phase 1: Pre-Integration Check](#phase-1-pre-integration-check)
|
||||
- [Phase 2: Incremental Installation](#phase-2-incremental-installation)
|
||||
- [Phase 3: Configuration](#phase-3-configuration)
|
||||
- [Phase 4: Schema Strategy](#phase-4-schema-strategy)
|
||||
- [Phase 5: Migration Handling](#phase-5-migration-handling)
|
||||
- [Phase 6: Coexistence Patterns](#phase-6-coexistence-patterns)
|
||||
- [Phase 7: Verification](#phase-7-verification)
|
||||
- [Phase 8: Add Best Practices References](#phase-8-add-best-practices-references)
|
||||
|
||||
---
|
||||
|
||||
## Workflow Checklist
|
||||
|
||||
When following this guide, I will track these high-level tasks:
|
||||
|
||||
- [ ] Pre-integration check (detect existing ORMs, database schema, environment)
|
||||
- [ ] Install Drizzle dependencies without disrupting existing setup
|
||||
- [ ] Create isolated Drizzle configuration (separate from existing code)
|
||||
- [ ] Choose and implement schema strategy (new tables vs mirroring existing)
|
||||
- [ ] Handle migrations safely based on schema strategy
|
||||
- [ ] Set up coexistence patterns and gradual migration approach
|
||||
- [ ] Verify Drizzle integration without breaking existing functionality
|
||||
- [ ] Add Neon Drizzle best practices to project docs
|
||||
|
||||
---
|
||||
|
||||
## Phase 1: Pre-Integration Check
|
||||
|
||||
Before adding Drizzle, check for conflicts:
|
||||
|
||||
### 1.1. Check for Other ORMs
|
||||
|
||||
```bash
|
||||
grep -E '"(prisma|typeorm|sequelize|mongoose)"' package.json
|
||||
```
|
||||
|
||||
**If found:**
|
||||
- Consider migration strategy (coexistence vs replacement)
|
||||
- Document which tables use which ORM
|
||||
- Plan gradual migration if needed
|
||||
|
||||
### 1.2. Check Database Schema
|
||||
|
||||
Connect to your database and verify existing tables:
|
||||
```bash
|
||||
psql $DATABASE_URL -c "\dt"
|
||||
```
|
||||
|
||||
**Important:** Note existing tables - Drizzle should not conflict with them.
|
||||
|
||||
### 1.3. Check Environment Setup
|
||||
|
||||
```bash
|
||||
ls .env .env.local .env.production
|
||||
grep DATABASE_URL .env*
|
||||
```
|
||||
|
||||
**If DATABASE_URL exists:**
|
||||
- Verify connection string format is compatible with Neon (`postgresql://...`)
|
||||
- If it's a different database provider, you'll need to migrate or provision a Neon database
|
||||
|
||||
**If DATABASE_URL does NOT exist:**
|
||||
Follow the database provisioning steps from `guides/new-project.md` Phase 3.1:
|
||||
1. List the projects using the neon MCP Server to check existing projects
|
||||
2. Create a new project using the neon MCP Server if needed
|
||||
3. Get the connection string using the neon MCP Server
|
||||
4. Write to appropriate environment file (.env.local for Next.js, .env for others)
|
||||
5. Add environment file to .gitignore
|
||||
|
||||
## Phase 2: Incremental Installation
|
||||
|
||||
Add Drizzle without disrupting existing setup:
|
||||
|
||||
### 2.1. Install Dependencies
|
||||
|
||||
**For Vercel/Edge:**
|
||||
```bash
|
||||
[package-manager] add drizzle-orm @neondatabase/serverless
|
||||
[package-manager] add -D drizzle-kit dotenv
|
||||
```
|
||||
|
||||
**For Node.js:**
|
||||
```bash
|
||||
[package-manager] add drizzle-orm @neondatabase/serverless ws
|
||||
[package-manager] add -D drizzle-kit dotenv @types/ws
|
||||
```
|
||||
|
||||
### 2.2. Create Isolated Drizzle Directory
|
||||
|
||||
Keep Drizzle separate from existing code:
|
||||
```bash
|
||||
mkdir -p src/drizzle
|
||||
```
|
||||
|
||||
Structure:
|
||||
```
|
||||
src/drizzle/
|
||||
├── index.ts # Connection
|
||||
├── schema.ts # New schemas only
|
||||
└── migrations/ # Drizzle migrations
|
||||
```
|
||||
|
||||
## Phase 3: Configuration
|
||||
|
||||
### 3.1. Create Drizzle Config
|
||||
|
||||
Create `drizzle.config.ts` with explicit environment loading:
|
||||
|
||||
**CRITICAL:** The `config({ path: '...' })` must match your environment file name.
|
||||
|
||||
**For Next.js (using .env.local):**
|
||||
```typescript
|
||||
import { defineConfig } from 'drizzle-kit';
|
||||
import { config } from 'dotenv';
|
||||
|
||||
// Load .env.local explicitly
|
||||
config({ path: '.env.local' });
|
||||
|
||||
export default defineConfig({
|
||||
schema: './src/drizzle/schema.ts',
|
||||
out: './src/drizzle/migrations',
|
||||
dialect: 'postgresql',
|
||||
dbCredentials: {
|
||||
url: process.env.DATABASE_URL!,
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
**For other projects (using .env):**
|
||||
```typescript
|
||||
import { defineConfig } from 'drizzle-kit';
|
||||
import { config } from 'dotenv';
|
||||
|
||||
// Load .env explicitly
|
||||
config({ path: '.env' });
|
||||
|
||||
export default defineConfig({
|
||||
schema: './src/drizzle/schema.ts',
|
||||
out: './src/drizzle/migrations',
|
||||
dialect: 'postgresql',
|
||||
dbCredentials: {
|
||||
url: process.env.DATABASE_URL!,
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
**Notes:**
|
||||
- Point schema and migrations to `src/drizzle/` to avoid conflicts with existing code
|
||||
- Explicit dotenv path prevents "url: undefined" errors during migrations
|
||||
|
||||
### 3.2. Create Connection
|
||||
|
||||
`src/drizzle/index.ts` - Choose adapter based on environment (see `references/adapters.md`):
|
||||
|
||||
**HTTP (Vercel/Edge):**
|
||||
```typescript
|
||||
import { drizzle } from 'drizzle-orm/neon-http';
|
||||
import { neon } from '@neondatabase/serverless';
|
||||
|
||||
const sql = neon(process.env.DATABASE_URL!);
|
||||
export const drizzleDb = drizzle(sql);
|
||||
```
|
||||
|
||||
**WebSocket (Node.js):**
|
||||
```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 drizzleDb = drizzle(pool);
|
||||
```
|
||||
|
||||
**Important:** Name export as `drizzleDb` to avoid conflicts with existing `db` exports.
|
||||
|
||||
## Phase 4: Schema Strategy
|
||||
|
||||
Choose integration approach:
|
||||
|
||||
### 4.1. Option A: New Tables Only
|
||||
|
||||
Create schemas for new features only, leave existing tables alone:
|
||||
|
||||
`src/drizzle/schema.ts`:
|
||||
```typescript
|
||||
import { pgTable, serial, text, timestamp } from 'drizzle-orm/pg-core';
|
||||
|
||||
export const newFeatureTable = pgTable('new_feature', {
|
||||
id: serial('id').primaryKey(),
|
||||
data: text('data').notNull(),
|
||||
createdAt: timestamp('created_at').defaultNow(),
|
||||
});
|
||||
```
|
||||
|
||||
**Pros:**
|
||||
- No migration of existing data
|
||||
- Zero risk to current functionality
|
||||
- Gradual adoption
|
||||
|
||||
**Cons:**
|
||||
- Mixed query patterns (Drizzle + existing ORM)
|
||||
- Two connection patterns in codebase
|
||||
|
||||
### 4.2. Option B: Mirror Existing Tables
|
||||
|
||||
Define schemas for existing tables to gradually migrate queries:
|
||||
|
||||
```typescript
|
||||
import { pgTable, serial, varchar, timestamp } from 'drizzle-orm/pg-core';
|
||||
|
||||
export const existingUsers = pgTable('users', {
|
||||
id: serial('id').primaryKey(),
|
||||
email: varchar('email', { length: 255 }).notNull(),
|
||||
name: varchar('name', { length: 255 }),
|
||||
createdAt: timestamp('created_at'),
|
||||
});
|
||||
```
|
||||
|
||||
**Pros:**
|
||||
- Can query existing data with Drizzle
|
||||
- Gradually replace old ORM queries
|
||||
- Type-safe access to existing tables
|
||||
|
||||
**Cons:**
|
||||
- Must match existing schema exactly
|
||||
- Requires careful migration strategy
|
||||
|
||||
### 4.3. Recommended: Hybrid Approach
|
||||
|
||||
1. Start with Option A (new tables only)
|
||||
2. Once comfortable, add schemas for frequently-queried existing tables (Option B)
|
||||
3. Gradually migrate queries from old ORM to Drizzle
|
||||
4. Eventually remove old ORM
|
||||
|
||||
## Phase 5: Migration Handling
|
||||
|
||||
### 5.1. For New Tables
|
||||
|
||||
Generate and run migrations normally:
|
||||
```bash
|
||||
[package-manager] drizzle-kit generate
|
||||
export DATABASE_URL="$(grep DATABASE_URL .env.local | cut -d '=' -f2)" && \
|
||||
[package-manager] drizzle-kit migrate
|
||||
```
|
||||
|
||||
### 5.2. For Existing Tables
|
||||
|
||||
**Do NOT run migrations** - tables already exist!
|
||||
|
||||
Instead, use Drizzle schemas for querying only:
|
||||
```typescript
|
||||
import { drizzleDb } from './drizzle';
|
||||
import { existingUsers } from './drizzle/schema';
|
||||
|
||||
const users = await drizzleDb.select().from(existingUsers);
|
||||
```
|
||||
|
||||
### 5.3. Mixed Scenario
|
||||
|
||||
If you have both new and existing tables:
|
||||
1. Define all schemas in `schema.ts`
|
||||
2. Run `drizzle-kit generate`
|
||||
3. **Manually edit** generated migration to remove SQL for existing tables
|
||||
4. Apply migration
|
||||
|
||||
See `references/migrations.md` for advanced patterns.
|
||||
|
||||
### 5.4. Add Migration Scripts
|
||||
|
||||
Add these convenience scripts to your `package.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"scripts": {
|
||||
"db:generate": "drizzle-kit generate",
|
||||
"db:migrate": "drizzle-kit migrate",
|
||||
"db:push": "drizzle-kit push",
|
||||
"db:studio": "drizzle-kit studio"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Usage:**
|
||||
```bash
|
||||
npm run db:generate # Generate migrations from schema changes
|
||||
npm run db:migrate # Apply pending migrations
|
||||
npm run db:push # Push schema directly (dev only)
|
||||
npm run db:studio # Open Drizzle Studio
|
||||
```
|
||||
|
||||
**Note:** Replace `npm run` with your package manager's equivalent (`pnpm`, `yarn`, `bun`).
|
||||
|
||||
## Phase 6: Coexistence Patterns
|
||||
|
||||
### 6.1. Naming Conventions
|
||||
|
||||
Keep clear separation:
|
||||
```typescript
|
||||
import { db as prismaDb } from './lib/prisma';
|
||||
import { drizzleDb } from './drizzle';
|
||||
|
||||
const prismaUsers = await prismaDb.user.findMany();
|
||||
const drizzleFeatures = await drizzleDb.select().from(newFeatureTable);
|
||||
```
|
||||
|
||||
### 6.2. Gradual Migration
|
||||
|
||||
**Step 1:** New features use Drizzle
|
||||
```typescript
|
||||
async function createFeature(data: NewFeatureInput) {
|
||||
return drizzleDb.insert(newFeatureTable).values(data).returning();
|
||||
}
|
||||
```
|
||||
|
||||
**Step 2:** Migrate read queries (safe, no data changes)
|
||||
```typescript
|
||||
async function getUsers() {
|
||||
return drizzleDb.select().from(existingUsers);
|
||||
}
|
||||
```
|
||||
|
||||
**Step 3:** Migrate write queries (after thorough testing)
|
||||
```typescript
|
||||
async function updateUser(id: number, data: UserUpdate) {
|
||||
return drizzleDb.update(existingUsers)
|
||||
.set(data)
|
||||
.where(eq(existingUsers.id, id));
|
||||
}
|
||||
```
|
||||
|
||||
**Step 4:** Remove old ORM once all queries migrated
|
||||
|
||||
## Phase 7: Verification
|
||||
|
||||
Test integration without breaking existing functionality:
|
||||
|
||||
### 7.1. Test New Tables
|
||||
|
||||
```typescript
|
||||
import { drizzleDb } from './drizzle';
|
||||
import { newFeatureTable } from './drizzle/schema';
|
||||
|
||||
const result = await drizzleDb.insert(newFeatureTable)
|
||||
.values({ data: 'test' })
|
||||
.returning();
|
||||
|
||||
console.log('New table works:', result);
|
||||
```
|
||||
|
||||
### 7.2. Test Existing Tables (if mirrored)
|
||||
|
||||
```typescript
|
||||
import { drizzleDb } from './drizzle';
|
||||
import { existingUsers } from './drizzle/schema';
|
||||
|
||||
const users = await drizzleDb.select().from(existingUsers);
|
||||
console.log('Existing table accessible:', users);
|
||||
```
|
||||
|
||||
### 7.3. Verify Old ORM Still Works
|
||||
|
||||
```typescript
|
||||
import { db as oldDb } from './lib/your-orm';
|
||||
|
||||
const oldQuery = await oldDb.users.findMany();
|
||||
console.log('Old ORM still works:', oldQuery);
|
||||
```
|
||||
|
||||
## Phase 8: Add Best Practices References
|
||||
|
||||
Before executing the add-neon-docs skill, provide a summary of everything that has been done:
|
||||
|
||||
"✅ ... Drizzle integration is complete! Now adding documentation references..."
|
||||
|
||||
Then execute the neon-plugin:add-neon-docs skill with the parameter SKILL_NAME="neon-drizzle"
|
||||
|
||||
This will add reference links to Neon + Drizzle best practices documentation in your project's AI documentation file, helping AI assistants provide better guidance in future conversations.
|
||||
|
||||
---
|
||||
|
||||
## ✅ Integration Complete!
|
||||
|
||||
Your Drizzle integration with the existing project is ready to use.
|
||||
|
||||
312
skills/neon-drizzle/guides/new-project.md
Normal file
312
skills/neon-drizzle/guides/new-project.md
Normal file
@@ -0,0 +1,312 @@
|
||||
# New Project Setup
|
||||
|
||||
> **Complete Walkthrough**: This is a self-contained, step-by-step guide with its own numbered phases (Phase 1-6).
|
||||
> Follow each phase in order for a full Drizzle + Neon setup from scratch.
|
||||
|
||||
Complete guide for setting up Drizzle ORM with Neon from scratch.
|
||||
|
||||
### Important:
|
||||
- Remember to run the neon-plugin:add-neon-docs skill with the parameter SKILL_NAME="neon-drizzle" after completing the guide.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [New Project Setup](#new-project-setup)
|
||||
- [Important:](#important)
|
||||
- [Table of Contents](#table-of-contents)
|
||||
- [Workflow Checklist](#workflow-checklist)
|
||||
- [Phase 1: Context Detection](#phase-1-context-detection)
|
||||
- [Phase 2: Installation](#phase-2-installation)
|
||||
- [Phase 3: Configuration](#phase-3-configuration)
|
||||
- [3.1. Neon Database Provisioning \& Environment File](#31-neon-database-provisioning--environment-file)
|
||||
- [3.2. Drizzle Config](#32-drizzle-config)
|
||||
- [3.3. Database Connection](#33-database-connection)
|
||||
- [Phase 4: Schema Generation](#phase-4-schema-generation)
|
||||
- [4.1. Common Patterns](#41-common-patterns)
|
||||
- [Phase 5: Migrations](#phase-5-migrations)
|
||||
- [5.1. Generate Migration](#51-generate-migration)
|
||||
- [5.2. Apply Migration](#52-apply-migration)
|
||||
- [5.3. Add Migration Scripts](#53-add-migration-scripts)
|
||||
- [5.4. If Migration Fails](#54-if-migration-fails)
|
||||
- [Phase 6: Add Best Practices References](#phase-6-add-best-practices-references)
|
||||
- [✅ Setup Complete!](#-setup-complete)
|
||||
|
||||
---
|
||||
|
||||
## Workflow Checklist
|
||||
|
||||
When following this guide, I will track these high-level tasks:
|
||||
|
||||
- [ ] Detect project context (package manager, framework, existing setup)
|
||||
- [ ] Install Drizzle dependencies based on deployment target
|
||||
- [ ] Provision Neon database (list projects, create if needed, get connection string)
|
||||
- [ ] Write connection string to environment file and verify
|
||||
- [ ] Create Drizzle configuration files (drizzle.config.ts, db connection)
|
||||
- [ ] Generate schema based on app type
|
||||
- [ ] Run and verify migrations
|
||||
- [ ] Add Neon Drizzle best practices to project docs
|
||||
|
||||
---
|
||||
|
||||
## Phase 1: Context Detection
|
||||
|
||||
Auto-detect project context:
|
||||
|
||||
**Check Package Manager:**
|
||||
```bash
|
||||
ls package-lock.json # → npm
|
||||
ls bun.lockb # → bun
|
||||
ls pnpm-lock.yaml # → pnpm
|
||||
ls yarn.lock # → yarn
|
||||
```
|
||||
|
||||
**Check Framework:**
|
||||
```bash
|
||||
grep '"next"' package.json # → Next.js
|
||||
grep '"express"' package.json # → Express
|
||||
grep '"vite"' package.json # → Vite
|
||||
```
|
||||
|
||||
**Check Existing Setup:**
|
||||
```bash
|
||||
ls drizzle.config.ts # Already configured?
|
||||
ls src/db/schema.ts # Schema exists?
|
||||
```
|
||||
|
||||
**Check Environment Files:**
|
||||
```bash
|
||||
ls .env .env.local .env.production
|
||||
```
|
||||
|
||||
## Phase 2: Installation
|
||||
|
||||
Based on detection, install dependencies:
|
||||
|
||||
**For Vercel/Edge Environments (Next.js, Vite on Vercel):**
|
||||
```bash
|
||||
[package-manager] add drizzle-orm @neondatabase/serverless
|
||||
[package-manager] add -D drizzle-kit dotenv @vercel/node
|
||||
```
|
||||
|
||||
**For Node.js Servers (Express, Fastify, standard Node):**
|
||||
```bash
|
||||
[package-manager] add drizzle-orm @neondatabase/serverless ws
|
||||
[package-manager] add -D drizzle-kit dotenv @types/ws
|
||||
```
|
||||
|
||||
## Phase 3: Configuration
|
||||
|
||||
Create configuration files in dependency order:
|
||||
|
||||
### 3.1. Neon Database Provisioning & Environment File
|
||||
|
||||
**Outcome**: A working `.env` or `.env.local` file with a real Neon connection string that the application can use immediately.
|
||||
|
||||
Use MCP tools to list or create a Neon project and get its connection string. Write the actual credentials to the environment file (`.env.local` for Next.js, `.env` for other projects). Add the file to `.gitignore`.
|
||||
|
||||
**Environment file format:**
|
||||
```bash
|
||||
DATABASE_URL=postgresql://user:password@host/database?sslmode=require
|
||||
```
|
||||
|
||||
### 3.2. Drizzle Config
|
||||
|
||||
Create `drizzle.config.ts` with explicit environment loading:
|
||||
|
||||
**CRITICAL:** The `config({ path: '...' })` line must match the environment file from Step 3.1.
|
||||
|
||||
**For Next.js (using .env.local):**
|
||||
```typescript
|
||||
import { defineConfig } from 'drizzle-kit';
|
||||
import { config } from 'dotenv';
|
||||
|
||||
// Load .env.local explicitly
|
||||
config({ path: '.env.local' });
|
||||
|
||||
export default defineConfig({
|
||||
schema: './src/db/schema.ts',
|
||||
out: './src/db/migrations',
|
||||
dialect: 'postgresql',
|
||||
dbCredentials: {
|
||||
url: process.env.DATABASE_URL!,
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
**For other projects (using .env):**
|
||||
```typescript
|
||||
import { defineConfig } from 'drizzle-kit';
|
||||
import { config } from 'dotenv';
|
||||
|
||||
// Load .env explicitly
|
||||
config({ path: '.env' });
|
||||
|
||||
export default defineConfig({
|
||||
schema: './src/db/schema.ts',
|
||||
out: './src/db/migrations',
|
||||
dialect: 'postgresql',
|
||||
dbCredentials: {
|
||||
url: process.env.DATABASE_URL!,
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
**Why this matters:**
|
||||
- Without explicit `config({ path: '...' })`, drizzle-kit may not load environment variables
|
||||
- This prevents "url: undefined" errors during migrations
|
||||
- The path must match your environment file name from Phase 3.1
|
||||
|
||||
### 3.3. Database Connection
|
||||
|
||||
Create `src/db/index.ts` with appropriate adapter (see `references/adapters.md` for decision guide):
|
||||
|
||||
**For Vercel/Edge:**
|
||||
```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);
|
||||
```
|
||||
|
||||
**For Node.js:**
|
||||
```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);
|
||||
```
|
||||
|
||||
See `templates/db-http.ts` and `templates/db-websocket.ts` for complete examples.
|
||||
|
||||
## Phase 4: Schema Generation
|
||||
|
||||
Based on app type, create appropriate schema:
|
||||
|
||||
### 4.1. Common Patterns
|
||||
|
||||
**Todo App:**
|
||||
```typescript
|
||||
import { pgTable, serial, text, boolean, timestamp, varchar } from 'drizzle-orm/pg-core';
|
||||
|
||||
export const users = pgTable('users', {
|
||||
id: serial('id').primaryKey(),
|
||||
email: varchar('email', { length: 255 }).notNull().unique(),
|
||||
name: varchar('name', { length: 255 }).notNull(),
|
||||
createdAt: timestamp('created_at').defaultNow(),
|
||||
});
|
||||
|
||||
export const todos = pgTable('todos', {
|
||||
id: serial('id').primaryKey(),
|
||||
userId: serial('user_id').notNull().references(() => users.id),
|
||||
title: text('title').notNull(),
|
||||
completed: boolean('completed').default(false),
|
||||
createdAt: timestamp('created_at').defaultNow(),
|
||||
});
|
||||
```
|
||||
|
||||
**Blog App:**
|
||||
```typescript
|
||||
import { pgTable, serial, text, timestamp, varchar, index } from 'drizzle-orm/pg-core';
|
||||
import { relations } from 'drizzle-orm';
|
||||
|
||||
export const users = pgTable('users', {
|
||||
id: serial('id').primaryKey(),
|
||||
email: varchar('email', { length: 255 }).notNull().unique(),
|
||||
name: varchar('name', { length: 255 }).notNull(),
|
||||
createdAt: timestamp('created_at').defaultNow(),
|
||||
});
|
||||
|
||||
export const posts = pgTable('posts', {
|
||||
id: serial('id').primaryKey(),
|
||||
userId: serial('user_id').notNull().references(() => users.id),
|
||||
title: text('title').notNull(),
|
||||
content: text('content').notNull(),
|
||||
createdAt: timestamp('created_at').defaultNow(),
|
||||
}, (table) => ({
|
||||
userIdIdx: index('posts_user_id_idx').on(table.userId),
|
||||
}));
|
||||
|
||||
export const usersRelations = relations(users, ({ many }) => ({
|
||||
posts: many(posts),
|
||||
}));
|
||||
|
||||
export const postsRelations = relations(posts, ({ one }) => ({
|
||||
author: one(users, {
|
||||
fields: [posts.userId],
|
||||
references: [users.id],
|
||||
}),
|
||||
}));
|
||||
```
|
||||
|
||||
See `templates/schema-example.ts` for more complex examples.
|
||||
|
||||
## Phase 5: Migrations
|
||||
|
||||
Run migrations with proper error handling:
|
||||
|
||||
### 5.1. Generate Migration
|
||||
|
||||
```bash
|
||||
[package-manager] drizzle-kit generate
|
||||
```
|
||||
|
||||
This creates SQL files in `src/db/migrations/`.
|
||||
|
||||
### 5.2. Apply Migration
|
||||
|
||||
**Recommended approach (explicit env loading):**
|
||||
```bash
|
||||
export DATABASE_URL="$(grep DATABASE_URL .env.local | cut -d '=' -f2)" && \
|
||||
[package-manager] drizzle-kit migrate
|
||||
```
|
||||
|
||||
**Why this works:** Ensures `DATABASE_URL` is available, preventing "url: undefined" errors.
|
||||
|
||||
### 5.3. Add Migration Scripts
|
||||
|
||||
Add these convenience scripts to your `package.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"scripts": {
|
||||
"db:generate": "drizzle-kit generate",
|
||||
"db:migrate": "drizzle-kit migrate",
|
||||
"db:push": "drizzle-kit push",
|
||||
"db:studio": "drizzle-kit studio"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Usage:**
|
||||
```bash
|
||||
npm run db:generate # Generate migrations from schema changes
|
||||
npm run db:migrate # Apply pending migrations
|
||||
npm run db:push # Push schema directly (dev only)
|
||||
npm run db:studio # Open Drizzle Studio
|
||||
```
|
||||
|
||||
**Note:** Replace `npm run` with your package manager's equivalent (`pnpm`, `yarn`, `bun`).
|
||||
|
||||
### 5.4. If Migration Fails
|
||||
|
||||
See `guides/troubleshooting.md` for common issues and fixes.
|
||||
|
||||
Also reference `references/migrations.md` for deep dive on migration patterns.
|
||||
|
||||
## Phase 6: Add Best Practices References
|
||||
|
||||
Before executing the add-neon-docs skill, provide a summary of everything that has been done:
|
||||
|
||||
"✅ ... Drizzle integration is complete! Now adding documentation references..."
|
||||
|
||||
Then execute the neon-plugin:add-neon-docs skill with the parameter SKILL_NAME="neon-drizzle"
|
||||
|
||||
This will add reference links to Neon + Drizzle best practices documentation in your project's AI documentation file, helping AI assistants provide better guidance in future conversations.
|
||||
|
||||
## ✅ Setup Complete!
|
||||
|
||||
Your Drizzle + Neon integration is ready to use.
|
||||
|
||||
415
skills/neon-drizzle/guides/schema-only.md
Normal file
415
skills/neon-drizzle/guides/schema-only.md
Normal file
@@ -0,0 +1,415 @@
|
||||
# Schema Creation and Modification
|
||||
|
||||
> **Complete Walkthrough**: This is a self-contained, step-by-step guide with its own numbered phases (Phase 1-6).
|
||||
> Follow each phase in order for schema design, modification, and migration workflows.
|
||||
|
||||
Guide for creating or modifying database schemas with Drizzle.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Workflow Checklist](#workflow-checklist)
|
||||
- [Phase 1: Schema Design Patterns](#phase-1-schema-design-patterns)
|
||||
- [Phase 2: Common Schema Patterns](#phase-2-common-schema-patterns)
|
||||
- [Phase 3: Schema Modifications](#phase-3-schema-modifications)
|
||||
- [Phase 4: Indexes and Constraints](#phase-4-indexes-and-constraints)
|
||||
- [Phase 5: Generate and Apply Changes](#phase-5-generate-and-apply-changes)
|
||||
- [Phase 6: Advanced Patterns](#phase-6-advanced-patterns)
|
||||
- [Common Issues](#common-issues)
|
||||
- [Next Steps](#next-steps)
|
||||
|
||||
---
|
||||
|
||||
## Workflow Checklist
|
||||
|
||||
When following this guide, I will track these high-level tasks:
|
||||
|
||||
- [ ] Design schema using appropriate patterns (tables, relationships, types)
|
||||
- [ ] Apply common schema patterns (auth, soft deletes, enums, JSON)
|
||||
- [ ] Implement schema modifications (add/rename/drop columns, change types)
|
||||
- [ ] Add indexes and constraints for performance and data integrity
|
||||
- [ ] Generate and apply migrations
|
||||
- [ ] Verify changes and test with queries
|
||||
|
||||
---
|
||||
|
||||
## Phase 1: Schema Design Patterns
|
||||
|
||||
### 1.1. Basic Table Structure
|
||||
|
||||
```typescript
|
||||
import { pgTable, serial, text, varchar, timestamp, boolean } from 'drizzle-orm/pg-core';
|
||||
|
||||
export const tableName = pgTable('table_name', {
|
||||
id: serial('id').primaryKey(),
|
||||
name: varchar('name', { length: 255 }).notNull(),
|
||||
description: text('description'),
|
||||
isActive: boolean('is_active').default(true),
|
||||
createdAt: timestamp('created_at').defaultNow(),
|
||||
updatedAt: timestamp('updated_at').defaultNow(),
|
||||
});
|
||||
```
|
||||
|
||||
**Key conventions:**
|
||||
- Use `serial` for auto-incrementing IDs
|
||||
- Use `varchar` for short strings (with length limit)
|
||||
- Use `text` for long strings
|
||||
- Use `timestamp` for dates/times
|
||||
- Always add `createdAt` for audit trails
|
||||
|
||||
### 1.2. Relationships
|
||||
|
||||
**One-to-Many:**
|
||||
```typescript
|
||||
import { pgTable, serial, text, timestamp, index } from 'drizzle-orm/pg-core';
|
||||
|
||||
export const authors = pgTable('authors', {
|
||||
id: serial('id').primaryKey(),
|
||||
name: text('name').notNull(),
|
||||
});
|
||||
|
||||
export const posts = pgTable('posts', {
|
||||
id: serial('id').primaryKey(),
|
||||
authorId: serial('author_id')
|
||||
.notNull()
|
||||
.references(() => authors.id),
|
||||
title: text('title').notNull(),
|
||||
content: text('content').notNull(),
|
||||
}, (table) => ({
|
||||
authorIdIdx: index('posts_author_id_idx').on(table.authorId),
|
||||
}));
|
||||
```
|
||||
|
||||
**Important:** Always add index on foreign keys for query performance.
|
||||
|
||||
**Many-to-Many:**
|
||||
```typescript
|
||||
export const posts = pgTable('posts', {
|
||||
id: serial('id').primaryKey(),
|
||||
title: text('title').notNull(),
|
||||
});
|
||||
|
||||
export const tags = pgTable('tags', {
|
||||
id: serial('id').primaryKey(),
|
||||
name: text('name').notNull(),
|
||||
});
|
||||
|
||||
export const postsTags = pgTable('posts_tags', {
|
||||
postId: serial('post_id')
|
||||
.notNull()
|
||||
.references(() => posts.id),
|
||||
tagId: serial('tag_id')
|
||||
.notNull()
|
||||
.references(() => tags.id),
|
||||
}, (table) => ({
|
||||
pk: index('posts_tags_pk').on(table.postId, table.tagId),
|
||||
}));
|
||||
```
|
||||
|
||||
### 1.3. Type-Safe Relations
|
||||
|
||||
Enable relational queries:
|
||||
```typescript
|
||||
import { relations } from 'drizzle-orm';
|
||||
|
||||
export const authorsRelations = relations(authors, ({ many }) => ({
|
||||
posts: many(posts),
|
||||
}));
|
||||
|
||||
export const postsRelations = relations(posts, ({ one }) => ({
|
||||
author: one(authors, {
|
||||
fields: [posts.authorId],
|
||||
references: [authors.id],
|
||||
}),
|
||||
}));
|
||||
```
|
||||
|
||||
**Benefits:**
|
||||
- Type-safe joins
|
||||
- Automatic loading of related data
|
||||
- No manual JOIN queries needed
|
||||
|
||||
## Phase 2: Common Schema Patterns
|
||||
|
||||
### 2.1. User Authentication
|
||||
|
||||
```typescript
|
||||
import { pgTable, serial, varchar, timestamp, boolean } from 'drizzle-orm/pg-core';
|
||||
|
||||
export const users = pgTable('users', {
|
||||
id: serial('id').primaryKey(),
|
||||
email: varchar('email', { length: 255 }).notNull().unique(),
|
||||
passwordHash: varchar('password_hash', { length: 255 }),
|
||||
name: varchar('name', { length: 255 }).notNull(),
|
||||
emailVerified: boolean('email_verified').default(false),
|
||||
createdAt: timestamp('created_at').defaultNow(),
|
||||
lastLoginAt: timestamp('last_login_at'),
|
||||
});
|
||||
```
|
||||
|
||||
### 2.2. Soft Deletes
|
||||
|
||||
```typescript
|
||||
export const posts = pgTable('posts', {
|
||||
id: serial('id').primaryKey(),
|
||||
title: text('title').notNull(),
|
||||
content: text('content').notNull(),
|
||||
deletedAt: timestamp('deleted_at'),
|
||||
createdAt: timestamp('created_at').defaultNow(),
|
||||
});
|
||||
```
|
||||
|
||||
Query with soft deletes:
|
||||
```typescript
|
||||
const activePosts = await db
|
||||
.select()
|
||||
.from(posts)
|
||||
.where(isNull(posts.deletedAt));
|
||||
```
|
||||
|
||||
### 2.3. Enums
|
||||
|
||||
```typescript
|
||||
import { pgEnum, pgTable, serial, text } from 'drizzle-orm/pg-core';
|
||||
|
||||
export const statusEnum = pgEnum('status', ['draft', 'published', 'archived']);
|
||||
|
||||
export const posts = pgTable('posts', {
|
||||
id: serial('id').primaryKey(),
|
||||
title: text('title').notNull(),
|
||||
status: statusEnum('status').default('draft'),
|
||||
});
|
||||
```
|
||||
|
||||
### 2.4. JSON Fields
|
||||
|
||||
```typescript
|
||||
import { pgTable, serial, jsonb } from 'drizzle-orm/pg-core';
|
||||
|
||||
export const products = pgTable('products', {
|
||||
id: serial('id').primaryKey(),
|
||||
name: text('name').notNull(),
|
||||
metadata: jsonb('metadata').$type<{
|
||||
color?: string;
|
||||
size?: string;
|
||||
tags?: string[];
|
||||
}>(),
|
||||
});
|
||||
```
|
||||
|
||||
## Phase 3: Schema Modifications
|
||||
|
||||
### 3.1. Adding Columns
|
||||
|
||||
**Step 1:** Update schema:
|
||||
```typescript
|
||||
export const users = pgTable('users', {
|
||||
id: serial('id').primaryKey(),
|
||||
email: varchar('email', { length: 255 }).notNull(),
|
||||
phoneNumber: varchar('phone_number', { length: 20 }), // NEW
|
||||
});
|
||||
```
|
||||
|
||||
**Step 2:** Generate migration:
|
||||
```bash
|
||||
[package-manager] drizzle-kit generate
|
||||
```
|
||||
|
||||
**Step 3:** Apply migration:
|
||||
```bash
|
||||
export DATABASE_URL="$(grep DATABASE_URL .env.local | cut -d '=' -f2)" && \
|
||||
[package-manager] drizzle-kit migrate
|
||||
```
|
||||
|
||||
### 3.2. Renaming Columns
|
||||
|
||||
**Important:** Drizzle sees renames as drop + add. Manual migration required.
|
||||
|
||||
**Step 1:** Update schema:
|
||||
```typescript
|
||||
export const users = pgTable('users', {
|
||||
id: serial('id').primaryKey(),
|
||||
fullName: varchar('full_name', { length: 255 }), // was 'name'
|
||||
});
|
||||
```
|
||||
|
||||
**Step 2:** Generate migration (will create drop + add):
|
||||
```bash
|
||||
[package-manager] drizzle-kit generate
|
||||
```
|
||||
|
||||
**Step 3:** Edit migration file manually:
|
||||
```sql
|
||||
-- Change from:
|
||||
-- ALTER TABLE users DROP COLUMN name;
|
||||
-- ALTER TABLE users ADD COLUMN full_name VARCHAR(255);
|
||||
|
||||
-- To:
|
||||
ALTER TABLE users RENAME COLUMN name TO full_name;
|
||||
```
|
||||
|
||||
**Step 4:** Apply migration:
|
||||
```bash
|
||||
[package-manager] drizzle-kit migrate
|
||||
```
|
||||
|
||||
### 3.3. Dropping Columns
|
||||
|
||||
**Step 1:** Remove from schema:
|
||||
```typescript
|
||||
export const users = pgTable('users', {
|
||||
id: serial('id').primaryKey(),
|
||||
email: varchar('email', { length: 255 }).notNull(),
|
||||
// removed: phoneNumber
|
||||
});
|
||||
```
|
||||
|
||||
**Step 2:** Generate and apply:
|
||||
```bash
|
||||
[package-manager] drizzle-kit generate
|
||||
[package-manager] drizzle-kit migrate
|
||||
```
|
||||
|
||||
**Warning:** This permanently deletes data. Back up first!
|
||||
|
||||
### 3.4. Changing Column Types
|
||||
|
||||
**Step 1:** Update schema:
|
||||
```typescript
|
||||
export const posts = pgTable('posts', {
|
||||
id: serial('id').primaryKey(),
|
||||
views: bigint('views', { mode: 'number' }), // was: integer
|
||||
});
|
||||
```
|
||||
|
||||
**Step 2:** Generate migration:
|
||||
```bash
|
||||
[package-manager] drizzle-kit generate
|
||||
```
|
||||
|
||||
**Step 3:** Review generated SQL - may need data migration if incompatible types.
|
||||
|
||||
## Phase 4: Indexes and Constraints
|
||||
|
||||
### 4.1. Add Indexes
|
||||
|
||||
**Single column:**
|
||||
```typescript
|
||||
import { pgTable, serial, text, index } from 'drizzle-orm/pg-core';
|
||||
|
||||
export const posts = pgTable('posts', {
|
||||
id: serial('id').primaryKey(),
|
||||
title: text('title').notNull(),
|
||||
authorId: serial('author_id').notNull(),
|
||||
}, (table) => ({
|
||||
titleIdx: index('posts_title_idx').on(table.title),
|
||||
authorIdIdx: index('posts_author_id_idx').on(table.authorId),
|
||||
}));
|
||||
```
|
||||
|
||||
**Composite index:**
|
||||
```typescript
|
||||
export const posts = pgTable('posts', {
|
||||
id: serial('id').primaryKey(),
|
||||
authorId: serial('author_id').notNull(),
|
||||
status: text('status').notNull(),
|
||||
}, (table) => ({
|
||||
authorStatusIdx: index('posts_author_status_idx').on(table.authorId, table.status),
|
||||
}));
|
||||
```
|
||||
|
||||
### 4.2. Unique Constraints
|
||||
|
||||
**Single column:**
|
||||
```typescript
|
||||
export const users = pgTable('users', {
|
||||
id: serial('id').primaryKey(),
|
||||
email: varchar('email', { length: 255 }).notNull().unique(),
|
||||
});
|
||||
```
|
||||
|
||||
**Multiple columns:**
|
||||
```typescript
|
||||
import { pgTable, serial, text, unique } from 'drizzle-orm/pg-core';
|
||||
|
||||
export const postsTags = pgTable('posts_tags', {
|
||||
postId: serial('post_id').notNull(),
|
||||
tagId: serial('tag_id').notNull(),
|
||||
}, (table) => ({
|
||||
unq: unique('posts_tags_unique').on(table.postId, table.tagId),
|
||||
}));
|
||||
```
|
||||
|
||||
### 4.3. Check Constraints
|
||||
|
||||
```typescript
|
||||
import { pgTable, serial, integer, check } from 'drizzle-orm/pg-core';
|
||||
|
||||
export const products = pgTable('products', {
|
||||
id: serial('id').primaryKey(),
|
||||
price: integer('price').notNull(),
|
||||
discountedPrice: integer('discounted_price'),
|
||||
}, (table) => ({
|
||||
priceCheck: check('price_check', 'price >= 0'),
|
||||
discountCheck: check('discount_check', 'discounted_price < price'),
|
||||
}));
|
||||
```
|
||||
|
||||
## Phase 5: Generate and Apply Changes
|
||||
|
||||
### 5.1. Generate Migration
|
||||
|
||||
After any schema changes:
|
||||
```bash
|
||||
[package-manager] drizzle-kit generate
|
||||
```
|
||||
|
||||
Review generated SQL in `src/db/migrations/`.
|
||||
|
||||
### 5.2. Apply Migration
|
||||
|
||||
With proper environment loading:
|
||||
```bash
|
||||
export DATABASE_URL="$(grep DATABASE_URL .env.local | cut -d '=' -f2)" && \
|
||||
[package-manager] drizzle-kit migrate
|
||||
```
|
||||
|
||||
Or use the migration script:
|
||||
```bash
|
||||
[package-manager] tsx scripts/run-migration.ts
|
||||
```
|
||||
|
||||
### 5.3. Verify Changes
|
||||
|
||||
**Check in database:**
|
||||
```bash
|
||||
psql $DATABASE_URL -c "\d table_name"
|
||||
```
|
||||
|
||||
**Test with queries:**
|
||||
```typescript
|
||||
import { db } from './src/db';
|
||||
import { tableName } from './src/db/schema';
|
||||
|
||||
const result = await db.select().from(tableName);
|
||||
console.log('Schema works:', result);
|
||||
```
|
||||
|
||||
## Phase 6: Advanced Patterns
|
||||
|
||||
For complex schemas, see:
|
||||
- `templates/schema-example.ts` - Multi-table examples with relations
|
||||
- `references/migrations.md` - Advanced migration patterns
|
||||
|
||||
## Common Issues
|
||||
|
||||
- **Migration conflicts:** See `guides/troubleshooting.md`
|
||||
- **Relationship errors:** Ensure foreign keys reference correct columns
|
||||
- **Type mismatches:** Match TypeScript types with SQL types carefully
|
||||
|
||||
## Next Steps
|
||||
|
||||
After schema creation:
|
||||
1. Run migrations (see above)
|
||||
2. Create queries (see `references/query-patterns.md`)
|
||||
3. Add validation (use Zod or similar)
|
||||
4. Test thoroughly before production
|
||||
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