9.1 KiB
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
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:
ls package-lock.json # → npm
ls bun.lockb # → bun
ls pnpm-lock.yaml # → pnpm
ls yarn.lock # → yarn
Check Framework:
grep '"next"' package.json # → Next.js
grep '"express"' package.json # → Express
grep '"vite"' package.json # → Vite
Check Existing Setup:
ls drizzle.config.ts # Already configured?
ls src/db/schema.ts # Schema exists?
Check Environment Files:
ls .env .env.local .env.production
Phase 2: Installation
Based on detection, install dependencies:
For Vercel/Edge Environments (Next.js, Vite on Vercel):
[package-manager] add drizzle-orm @neondatabase/serverless
[package-manager] add -D drizzle-kit dotenv @vercel/node
For Node.js Servers (Express, Fastify, standard Node):
[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:
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):
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):
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:
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:
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:
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:
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
[package-manager] drizzle-kit generate
This creates SQL files in src/db/migrations/.
5.2. Apply Migration
Recommended approach (explicit env loading):
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:
{
"scripts": {
"db:generate": "drizzle-kit generate",
"db:migrate": "drizzle-kit migrate",
"db:push": "drizzle-kit push",
"db:studio": "drizzle-kit studio"
}
}
Usage:
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.