Some_claude_skills drizzle-migrations
Drizzle ORM schema management and SQLite migrations — adding tables, modifying columns, creating indexes, generating and running migrations, Drizzle query patterns. NOT for Prisma, TypeORM,
install
source · Clone the upstream repo
git clone https://github.com/curiositech/some_claude_skills
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/curiositech/some_claude_skills "$T" && mkdir -p ~/.claude/skills && cp -r "$T/.claude/skills/drizzle-migrations" ~/.claude/skills/erichowens-some-claude-skills-drizzle-migrations && rm -rf "$T"
manifest:
.claude/skills/drizzle-migrations/SKILL.mdsource content
Drizzle ORM Migrations
This skill helps you manage database schema changes using Drizzle ORM with SQLite.
When to Use
✅ USE this skill for:
- Adding new tables or modifying existing columns
- Generating and running database migrations
- Drizzle-specific query patterns and relations
- SQLite schema best practices with Drizzle
- Setting up Drizzle configuration
❌ DO NOT use for:
- Supabase/PostgreSQL → use
skillsupabase-admin - Raw SQL without Drizzle → use standard SQL resources
- Prisma ORM → different syntax and patterns
- General database design theory → use database architecture resources
Project Setup
Configuration:
drizzle.config.ts
import { defineConfig } from 'drizzle-kit'; export default defineConfig({ schema: './src/db/schema.ts', out: './drizzle', dialect: 'sqlite', dbCredentials: { url: './data/app.db', }, });
Commands:
npm run db:generate # Generate migration files npm run db:push # Push schema directly (dev only) npm run db:studio # Open Drizzle Studio GUI
Schema Definition
Location:
src/db/schema.ts
Table Definition
import { sqliteTable, text, integer, real, blob } from 'drizzle-orm/sqlite-core'; import { relations } from 'drizzle-orm'; // Basic table export const users = sqliteTable('users', { id: text('id').primaryKey(), email: text('email').notNull().unique(), username: text('username').notNull(), passwordHash: text('password_hash'), createdAt: text('created_at').notNull().default(sql`CURRENT_TIMESTAMP`), updatedAt: text('updated_at'), }); // Table with foreign key export const checkIns = sqliteTable('check_ins', { id: text('id').primaryKey(), userId: text('user_id').notNull().references(() => users.id, { onDelete: 'cascade', }), mood: integer('mood').notNull(), cravingLevel: integer('craving_level').notNull(), sleepHours: real('sleep_hours'), notes: text('notes'), createdAt: text('created_at').notNull().default(sql`CURRENT_TIMESTAMP`), }); // Table with composite index export const auditLog = sqliteTable('audit_log', { id: text('id').primaryKey(), userId: text('user_id').notNull(), action: text('action').notNull(), targetType: text('target_type'), targetId: text('target_id'), details: text('details'), // JSON string createdAt: text('created_at').notNull().default(sql`CURRENT_TIMESTAMP`), }, (table) => ({ userActionIdx: index('idx_audit_user_action').on(table.userId, table.action), createdAtIdx: index('idx_audit_created').on(table.createdAt), }));
Relations
export const usersRelations = relations(users, ({ many }) => ({ checkIns: many(checkIns), sessions: many(sessions), journalEntries: many(journalEntries), })); export const checkInsRelations = relations(checkIns, ({ one }) => ({ user: one(users, { fields: [checkIns.userId], references: [users.id], }), }));
Column Types
SQLite Types in Drizzle
import { sqliteTable, text, // TEXT - strings, JSON, dates integer, // INTEGER - numbers, booleans (0/1) real, // REAL - floating point blob, // BLOB - binary data } from 'drizzle-orm/sqlite-core'; const examples = sqliteTable('examples', { // Strings name: text('name').notNull(), description: text('description'), // Numbers count: integer('count').notNull().default(0), rating: real('rating'), // Booleans (stored as 0/1) isActive: integer('is_active', { mode: 'boolean' }).default(true), // Dates (stored as ISO strings) createdAt: text('created_at').notNull().default(sql`CURRENT_TIMESTAMP`), expiresAt: text('expires_at'), // JSON (stored as TEXT) metadata: text('metadata', { mode: 'json' }), // Enums (stored as TEXT) status: text('status', { enum: ['pending', 'active', 'archived'] }), });
Migration Strategies
Strategy 1: Push (Development Only)
npm run db:push
- Directly applies schema changes
- Fast for development
- Never use in production
Strategy 2: Generate & Migrate (Production)
# 1. Generate migration file npm run db:generate # 2. Review generated SQL in /drizzle folder # 3. Apply migration (in code or manually)
Applying Migrations in Code
import { drizzle } from 'drizzle-orm/better-sqlite3'; import { migrate } from 'drizzle-orm/better-sqlite3/migrator'; import Database from 'better-sqlite3'; const sqlite = new Database('./data/app.db'); const db = drizzle(sqlite); // Run migrations migrate(db, { migrationsFolder: './drizzle' });
Common Schema Changes
Adding a New Table
// 1. Add to schema.ts export const newFeature = sqliteTable('new_feature', { id: text('id').primaryKey(), userId: text('user_id').notNull().references(() => users.id), name: text('name').notNull(), createdAt: text('created_at').notNull().default(sql`CURRENT_TIMESTAMP`), }); // 2. Add relations export const newFeatureRelations = relations(newFeature, ({ one }) => ({ user: one(users, { fields: [newFeature.userId], references: [users.id], }), })); // 3. Generate migration // npm run db:generate
Adding a Column
// In schema.ts, add the new column export const users = sqliteTable('users', { // existing columns... newColumn: text('new_column'), // Add this }); // Generate migration // npm run db:generate
Adding an Index
export const messages = sqliteTable('messages', { id: text('id').primaryKey(), conversationId: text('conversation_id').notNull(), createdAt: text('created_at').notNull(), }, (table) => ({ // Add index convCreatedIdx: index('idx_messages_conv_created') .on(table.conversationId, table.createdAt), }));
Renaming (Requires Manual SQL)
SQLite doesn't support direct column renames in older versions. For complex changes:
-- drizzle/XXXX_rename_column.sql -- Manual migration for column rename -- 1. Create new table with desired schema CREATE TABLE users_new ( id TEXT PRIMARY KEY, email TEXT NOT NULL UNIQUE, display_name TEXT NOT NULL, -- renamed from username created_at TEXT NOT NULL ); -- 2. Copy data INSERT INTO users_new SELECT id, email, username, created_at FROM users; -- 3. Drop old table DROP TABLE users; -- 4. Rename new table ALTER TABLE users_new RENAME TO users;
Query Patterns
Basic Queries
import { db } from '@/db'; import { eq, and, or, desc, asc, like, gte, lte } from 'drizzle-orm'; import { users, checkIns } from '@/db/schema'; // Select all const allUsers = await db.select().from(users); // Select with conditions const activeUsers = await db .select() .from(users) .where(eq(users.isActive, true)); // Select specific columns const userEmails = await db .select({ id: users.id, email: users.email }) .from(users); // Complex where clause const results = await db .select() .from(checkIns) .where( and( eq(checkIns.userId, userId), gte(checkIns.createdAt, startDate), lte(checkIns.createdAt, endDate) ) ) .orderBy(desc(checkIns.createdAt)) .limit(30);
Insert
// Single insert const [newUser] = await db .insert(users) .values({ id: generateId(), email: 'user@example.com', username: 'newuser', }) .returning(); // Bulk insert await db.insert(checkIns).values([ { id: '1', userId, mood: 7, cravingLevel: 2 }, { id: '2', userId, mood: 8, cravingLevel: 1 }, ]); // Upsert (insert or update) await db .insert(users) .values({ id: 'user-1', email: 'new@example.com' }) .onConflictDoUpdate({ target: users.id, set: { email: 'new@example.com' }, });
Update
await db .update(users) .set({ username: 'newname', updatedAt: new Date().toISOString() }) .where(eq(users.id, userId));
Delete
// Always use WHERE clause! await db .delete(checkIns) .where(eq(checkIns.id, checkInId)); // Delete with multiple conditions await db .delete(sessions) .where( and( eq(sessions.userId, userId), lte(sessions.expiresAt, new Date().toISOString()) ) );
Joins
const userWithCheckIns = await db .select({ user: users, checkIn: checkIns, }) .from(users) .leftJoin(checkIns, eq(users.id, checkIns.userId)) .where(eq(users.id, userId));
Aggregations
import { count, avg, sum, max, min } from 'drizzle-orm'; const stats = await db .select({ totalCheckIns: count(), avgMood: avg(checkIns.mood), maxStreak: max(checkIns.streak), }) .from(checkIns) .where(eq(checkIns.userId, userId));
Best Practices
- Always use transactions for related changes
await db.transaction(async (tx) => { await tx.insert(users).values(userData); await tx.insert(profiles).values(profileData); });
- Always include WHERE on DELETE/UPDATE
- Use indexes for frequently queried columns
- Store dates as ISO strings for SQLite
- Use
to get inserted/updated rowsreturning() - Generate migrations, don't push to production