Files
gh-neondatabase-labs-ai-rul…/skills/neon-drizzle/guides/new-project.md
2025-11-30 08:43:11 +08:00

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


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.