CAPYSQUASH

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

Apply Migrations

# Push to database
pnpm drizzle-kit push:pg

# Or migrate
pnpm drizzle-kit migrate

Optimize 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=standard

Deploy Optimized Migrations

# For fresh environments
# Replace drizzle/ with optimized files
# Run: pnpm drizzle-kit migrate

# For existing: Use optimized for new deployments

BEST 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.sql

DRIZZLE + 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 deployments

Drizzle + 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 constraint

REAL-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 migrate

Verify 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 normally

DRIZZLE 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


Questions about Drizzle integration? Check our troubleshooting guide or reach out to support@capysquash.dev

How is this guide?

On this page