Initial commit

This commit is contained in:
Zhongwei Li
2025-11-30 08:43:11 +08:00
commit 5cf0559508
28 changed files with 5938 additions and 0 deletions

View 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.

View 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.

View 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

View 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