Drizzle ORM Integration: TypeScript-First Migration Optimization
Auto-detect Drizzle ORM patterns and optimize migrations - preserve relations, indexes, and schema definitions with zero configuration
DRIZZLE ORM INTEGRATION: TYPESCRIPT-FIRST MIGRATION OPTIMIZATION
Zero-configuration Drizzle pattern recognition - CAPYSQUASH automatically detects and optimizes Drizzle migrations while preserving your schema definitions.
Drizzle ORM is a lightweight, TypeScript-first ORM with SQL-like syntax and excellent performance. CAPYSQUASH understands Drizzle's migration format and optimizes your files while maintaining perfect compatibility with Drizzle's push, generate, and introspect commands.
AUTO-DETECTION IN ACTION
[plugins] Scanning migrations for patterns...
[plugins] Detected: Drizzle ORM migration format
[plugins] Found 18 table definitions
[plugins] Identified 31 relations
[plugins] Preserving custom types and indexes
[plugins] Ready for optimization with Drizzle support✨ No configuration needed! CAPYSQUASH recognizes Drizzle migrations automatically.
WHAT GETS DETECTED
Drizzle-Specific Patterns
CAPYSQUASH automatically recognizes:
SCHEMA PATTERNS
- ► Table definitions with pgTable()
- ► Column types and constraints
- ► Primary and foreign keys
- ► Unique constraints
- ► Custom PostgreSQL types
- ► Enum definitions
MIGRATION FILES
- ► Generated SQL migrations
- ► Meta JSON snapshot files
- ► Incremental migration format
- ► Relation definitions
- ► Index creation statements
- ► TypeScript schema files
Drizzle Relations
RELATION TYPES RECOGNIZED
ONE-TO-ONE
relations() with one() helper
ONE-TO-MANY
relations() with many() helper
MANY-TO-MANY
Junction tables with dual foreign keys
COMMON DRIZZLE MIGRATION PATTERNS
Pattern 1: Table Evolution
Before Optimization (7 migrations):
-- 0000_create_users.sql
CREATE TABLE IF NOT EXISTS "users" (
"id" serial PRIMARY KEY NOT NULL,
"created_at" timestamp DEFAULT now() NOT NULL
);
-- 0001_add_email.sql
ALTER TABLE "users" ADD COLUMN "email" text NOT NULL;
-- 0002_add_email_unique.sql
ALTER TABLE "users" ADD CONSTRAINT "users_email_unique" UNIQUE("email");
-- 0003_add_name.sql
ALTER TABLE "users" ADD COLUMN "name" text;
-- 0004_add_verified.sql
ALTER TABLE "users" ADD COLUMN "email_verified" boolean DEFAULT false NOT NULL;
-- 0005_add_updated_at.sql
ALTER TABLE "users" ADD COLUMN "updated_at" timestamp DEFAULT now() NOT NULL;
-- 0006_add_indexes.sql
CREATE INDEX IF NOT EXISTS "users_email_idx" ON "users" ("email");
CREATE INDEX IF NOT EXISTS "users_created_at_idx" ON "users" ("created_at");After Optimization (1 migration):
-- 0000_create_users_complete.sql
CREATE TABLE IF NOT EXISTS "users" (
"id" serial PRIMARY KEY NOT NULL,
"email" text NOT NULL,
"name" text,
"email_verified" boolean DEFAULT false NOT NULL,
"created_at" timestamp DEFAULT now() NOT NULL,
"updated_at" timestamp DEFAULT now() NOT NULL,
CONSTRAINT "users_email_unique" UNIQUE("email")
);
CREATE INDEX IF NOT EXISTS "users_email_idx" ON "users" ("email");
CREATE INDEX IF NOT EXISTS "users_created_at_idx" ON "users" ("created_at");7 files → 1 file (85.7% reduction) with perfect Drizzle compatibility
Pattern 2: Relations with Foreign Keys
Before Optimization (9 migrations):
-- 0000_create_posts.sql
CREATE TABLE IF NOT EXISTS "posts" (
"id" serial PRIMARY KEY NOT NULL,
"title" text NOT NULL
);
-- 0001_add_author_id.sql
ALTER TABLE "posts" ADD COLUMN "author_id" integer NOT NULL;
-- 0002_add_foreign_key.sql
ALTER TABLE "posts"
ADD CONSTRAINT "posts_author_id_users_id_fk"
FOREIGN KEY ("author_id") REFERENCES "users"("id")
ON DELETE CASCADE ON UPDATE NO ACTION;
-- 0003_create_comments.sql
CREATE TABLE IF NOT EXISTS "comments" (
"id" serial PRIMARY KEY NOT NULL,
"content" text NOT NULL
);
-- 0004_add_post_id.sql
ALTER TABLE "comments" ADD COLUMN "post_id" integer NOT NULL;
-- 0005_add_user_id.sql
ALTER TABLE "comments" ADD COLUMN "user_id" integer NOT NULL;
-- 0006_add_comment_post_fk.sql
ALTER TABLE "comments"
ADD CONSTRAINT "comments_post_id_posts_id_fk"
FOREIGN KEY ("post_id") REFERENCES "posts"("id");
-- 0007_add_comment_user_fk.sql
ALTER TABLE "comments"
ADD CONSTRAINT "comments_user_id_users_id_fk"
FOREIGN KEY ("user_id") REFERENCES "users"("id");
-- 0008_add_timestamps.sql
ALTER TABLE "posts" ADD COLUMN "created_at" timestamp DEFAULT now();
ALTER TABLE "comments" ADD COLUMN "created_at" timestamp DEFAULT now();After Optimization (2 migrations):
-- 0000_create_posts_complete.sql
CREATE TABLE IF NOT EXISTS "posts" (
"id" serial PRIMARY KEY NOT NULL,
"title" text NOT NULL,
"author_id" integer NOT NULL,
"created_at" timestamp DEFAULT now(),
CONSTRAINT "posts_author_id_users_id_fk"
FOREIGN KEY ("author_id") REFERENCES "users"("id")
ON DELETE CASCADE ON UPDATE NO ACTION
);
-- 0001_create_comments_complete.sql
CREATE TABLE IF NOT EXISTS "comments" (
"id" serial PRIMARY KEY NOT NULL,
"content" text NOT NULL,
"post_id" integer NOT NULL,
"user_id" integer NOT NULL,
"created_at" timestamp DEFAULT now(),
CONSTRAINT "comments_post_id_posts_id_fk"
FOREIGN KEY ("post_id") REFERENCES "posts"("id"),
CONSTRAINT "comments_user_id_users_id_fk"
FOREIGN KEY ("user_id") REFERENCES "users"("id")
);9 files → 2 files (77.8% reduction) with all relations preserved
OPTIMIZATION STRATEGIES
Drizzle Column Types
CAPYSQUASH preserves all Drizzle PostgreSQL types:
TYPE PRESERVATION
// Drizzle schema types → SQL patterns preserved
// Numeric types
integer('count') → integer
serial('id') → serial PRIMARY KEY
bigint('amount', { mode: 'number' }) → bigint
// Text types
text('description') → text
varchar('name', { length: 255 }) → varchar(255)
char('code', { length: 10 }) → char(10)
// Date/time types
timestamp('created_at') → timestamp
date('birth_date') → date
time('appointment_time') → time
// Boolean
boolean('is_active') → boolean
// JSON
json('metadata') → json
jsonb('settings') → jsonb
// All types recognized and consolidated correctly!Custom PostgreSQL Types
ENUM AND CUSTOM TYPES
-- Enum type consolidation
DO $$ BEGIN
CREATE TYPE "role" AS ENUM('user', 'admin');
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
-- Multiple enum additions optimized
CREATE TYPE "status" AS ENUM('draft', 'published', 'archived');
-- Custom composite types preserved
CREATE TYPE "address" AS (
street text,
city text,
country text
);Index Optimization
INDEX CONSOLIDATION
-- Before: Scattered across migrations
CREATE INDEX IF NOT EXISTS "idx_users_email" ON "users"("email");
-- ... later ...
CREATE INDEX IF NOT EXISTS "idx_users_name" ON "users"("name");
-- ... later ...
CREATE INDEX IF NOT EXISTS "idx_users_created" ON "users"("created_at");
-- After: Consolidated intelligently
CREATE INDEX IF NOT EXISTS "idx_users_email" ON "users"("email");
CREATE INDEX IF NOT EXISTS "idx_users_name" ON "users"("name");
CREATE INDEX IF NOT EXISTS "idx_users_created" ON "users"("created_at");
-- Partial indexes preserved
CREATE INDEX IF NOT EXISTS "idx_active_users"
ON "users"("email") WHERE "is_active" = true;DRIZZLE WORKFLOW INTEGRATION
Development Workflow
Develop with Drizzle
# Update your schema
nano src/schema.ts
# Generate migration
pnpm drizzle-kit generate:pg
# Generated: drizzle/0001_add_user_fields.sqlApply Migrations
# Push to database
pnpm drizzle-kit push:pg
# Or migrate
pnpm drizzle-kit migrateOptimize with CAPYSQUASH
After accumulating migrations:
# Option 1: Platform (Recommended)
# Upload drizzle/ folder to CAPYSQUASH
# Click "Optimize" → Download optimized files
# Option 2: CLI
capysquash analyze drizzle/*.sql
capysquash squash --safety=standardDeploy Optimized Migrations
# For fresh environments
# Replace drizzle/ with optimized files
# Run: pnpm drizzle-kit migrate
# For existing: Use optimized for new deploymentsBEST PRACTICES
Drizzle Schema Design
☑ DO THIS
- ► Use descriptive table names (camelCase or snake_case)
- ► Define relations() for clarity
- ► Add timestamps (createdAt, updatedAt)
- ► Use serial or uuid for IDs
- ► Include NOT NULL where appropriate
- ► Add indexes for foreign keys
☒ AVOID THIS
- ► Don't manually edit generated SQL
- ► Don't skip migration generation
- ► Don't delete old migrations
- ► Don't mix Drizzle with manual DDL
- ► Don't ignore schema drift warnings
- ► Don't use generic column names
Migration Naming
CUSTOM MIGRATION NAMES
# Good: Descriptive names
pnpm drizzle-kit generate:pg --name add_user_email
pnpm drizzle-kit generate:pg --name create_posts_table
pnpm drizzle-kit generate:pg --name add_post_author_relation
# Drizzle auto-generates timestamps
# 0000_add_user_email.sql
# 0001_create_posts_table.sql
# 0002_add_post_author_relation.sqlDRIZZLE + OTHER SERVICES
Drizzle + Neon
SERVERLESS OPTIMIZATION
Drizzle with Neon's serverless PostgreSQL:
// drizzle.config.ts
import type { Config } from 'drizzle-kit';
export default {
schema: './src/schema.ts',
out: './drizzle',
driver: 'pg',
dbCredentials: {
connectionString: process.env.NEON_DATABASE_URL!,
},
} satisfies Config;
// CAPYSQUASH optimizes for Neon's connection pooling!- ► Optimized for Neon's auto-pause
- ► Connection pooling friendly
- ► Fast cold starts with fewer files
Drizzle + Vercel Postgres
EDGE RUNTIME READY
import { drizzle } from 'drizzle-orm/vercel-postgres';
import { sql } from '@vercel/postgres';
import * as schema from './schema';
const db = drizzle(sql, { schema });
// Optimized migrations = faster edge deploymentsDrizzle + Supabase
HYBRID APPROACH
Use Drizzle ORM with Supabase database:
import { drizzle } from 'drizzle-orm/postgres-js';
import postgres from 'postgres';
const connectionString = process.env.SUPABASE_DATABASE_URL!;
const client = postgres(connectionString);
const db = drizzle(client);
// CAPYSQUASH detects both Drizzle and Supabase patterns!DRIZZLE RELATIONS OPTIMIZATION
Defining Relations
RELATION PATTERNS
// schema.ts - Drizzle relations
import { relations } from 'drizzle-orm';
import { pgTable, serial, text, integer } from 'drizzle-orm/pg-core';
export const users = pgTable('users', {
id: serial('id').primaryKey(),
name: text('name').notNull(),
});
export const posts = pgTable('posts', {
id: serial('id').primaryKey(),
title: text('title').notNull(),
authorId: integer('author_id').notNull(),
});
export const usersRelations = relations(users, ({ many }) => ({
posts: many(posts),
}));
export const postsRelations = relations(posts, ({ one }) => ({
author: one(users, {
fields: [posts.authorId],
references: [users.id],
}),
}));
// Generated SQL foreign keys are preserved perfectly!TROUBLESHOOTING
Common Issues
⚠️ SCHEMA DRIFT DETECTED
Problem: Database schema doesn't match Drizzle definitions
Solution: Introspect and regenerate:
# Pull current database state
pnpm drizzle-kit introspect:pg
# Generate migration to sync
pnpm drizzle-kit generate:pg --name sync_schema
# Then optimize with CAPYSQUASH⚠️ MISSING FOREIGN KEYS
Problem: Relations defined but foreign keys missing in SQL
Solution: Use references() in schema:
// Make sure to use .references()
export const posts = pgTable('posts', {
id: serial('id').primaryKey(),
authorId: integer('author_id')
.notNull()
.references(() => users.id, { onDelete: 'cascade' }),
});
// This generates the foreign key constraintREAL-WORLD EXAMPLE
Full-Stack TypeScript App
Starting point: 52 Drizzle migration files over 4 months
BEFORE OPTIMIZATION
drizzle/
├── 0000_init_users.sql
├── 0001_add_email.sql
├── 0002_create_posts.sql
├── 0003_add_post_author.sql
├── 0004_create_comments.sql
...
├── 0051_add_final_indexes.sql
(52 total migrations)CAPYSQUASH Analysis:
- Detected Drizzle ORM format
- Found 8 table definitions
- Identified 15 foreign key relations
- Recognized 4 enum types
- Preserved all TypeScript mappings
AFTER OPTIMIZATION
optimized_drizzle/
├── 0000_create_core_tables.sql (merged 0-15)
├── 0001_add_relations.sql (merged 16-30)
├── 0002_add_enums_types.sql (merged 31-40)
└── 0003_add_indexes.sql (merged 41-52)
(4 total migrations)Results:
- 📊 52 files → 4 files (92.3% reduction)
- ⏱️ Deployment time: 9 min → 45 sec (91.7% faster)
- ☑ Perfect Drizzle compatibility
- 🔒 All relations preserved
- 📦 Git repository 1.8 MB smaller
- 🚀 Vercel deployment 3x faster
VERIFICATION
Test Optimized Migrations
Fresh Database Test
# Create test database
createdb test_drizzle_optimized
# Set connection string
export DATABASE_URL="postgresql://localhost/test_drizzle_optimized"
# Apply optimized migrations
pnpm drizzle-kit migrateVerify Schema
# Push schema to verify sync
pnpm drizzle-kit push:pg
# Should show: "No schema changes detected"Run Your App
# Start your application
pnpm dev
# Test queries work correctly
# All relations should function normallyDRIZZLE STUDIO COMPATIBILITY
VISUAL SCHEMA EXPLORER
Optimized migrations work perfectly with Drizzle Studio:
# Launch Drizzle Studio
pnpm drizzle-kit studio
# Opens at: https://local.drizzle.studio
# All tables, relations, and data visible
# No difference from original migrations!☑ Schema introspection works identically with optimized migrations
NEXT STEPS
🚀 GET STARTED
📚 RELATED GUIDES
Questions about Drizzle integration? Check our troubleshooting guide or reach out to support@capysquash.dev
How is this guide?