| name | drizzle-migrations |
| description | Drizzle ORM schema management and SQLite migrations — adding tables, modifying columns, creating indexes, generating and running migrations, Drizzle query patterns. NOT for Prisma, TypeORM, Sequelize, or raw SQL migration tools. |
| allowed-tools | Read,Write,Edit,Bash(npm:*,npx:*) |
| metadata | {"category":"Data & Analytics","tags":["database","drizzle","migrations"],"pairs-with":[{"skill":"database-design-patterns","reason":"Schema migration implementation follows database design pattern decisions"},{"skill":"supabase-admin","reason":"Drizzle migrations run against Supabase PostgreSQL databases with RLS considerations"},{"skill":"fullstack-debugger","reason":"Migration failures and schema mismatches are common fullstack debugging scenarios"}]} |
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
supabase-admin skill
- 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
npm run db:push
npm run db:studio
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';
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'),
});
export const checkIns = sqliteTable('check_ins', {
id: text('id').primaryKey(),
userId: text('user_id').notNull().references(() => users., {
: ,
}),
: ().(),
: ().(),
: (),
: (),
: ().().(sql),
});
auditLog = (, {
: ().(),
: ().(),
: ().(),
: (),
: (),
: (),
: ().().(sql),
}, ({
: ().(table., table.),
: ().(table.),
}));
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,
integer,
real,
blob,
} from 'drizzle-orm/sqlite-core';
const examples = sqliteTable('examples', {
name: text('name').notNull(),
description: text('description'),
count: integer('count').notNull().default(0),
rating: real('rating'),
isActive: integer('is_active', { mode: 'boolean' }).default(true),
createdAt: text('created_at').notNull().default(sql`CURRENT_TIMESTAMP`),
expiresAt: text('expires_at'),
metadata: text(, { : }),
: (, { : [, , ] }),
});
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)
npm run db:generate
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);
migrate(db, { migrationsFolder: './drizzle' });
Common Schema Changes
Adding a New Table
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`),
});
export const newFeatureRelations = relations(newFeature, ({ one }) => ({
user: one(users, {
fields: [newFeature.userId],
references: [users.id],
}),
}));
Adding a Column
export const users = sqliteTable('users', {
newColumn: text('new_column'),
});
Adding an Index
export const messages = sqliteTable('messages', {
id: text('id').primaryKey(),
conversationId: text('conversation_id').notNull(),
createdAt: text('created_at').notNull(),
}, (table) => ({
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:
CREATE TABLE users_new (
id TEXT PRIMARY KEY,
email TEXT NOT NULL UNIQUE,
display_name TEXT NOT NULL,
created_at TEXT NOT NULL
);
INSERT INTO users_new SELECT id, email, username, created_at FROM users;
DROP TABLE users;
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';
const allUsers = await db.select().from(users);
const activeUsers = await db
.select()
.from(users)
.where(eq(users.isActive, true));
const userEmails = await db
.select({ id: users.id, email: users.email })
.from(users);
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))
.();
Insert
const [newUser] = await db
.insert(users)
.values({
id: generateId(),
email: 'user@example.com',
username: 'newuser',
})
.returning();
await db.insert(checkIns).values([
{ id: '1', userId, mood: 7, cravingLevel: 2 },
{ id: '2', userId, mood: 8, cravingLevel: 1 },
]);
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
await db
.delete(checkIns)
.where(eq(checkIns.id, checkInId));
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
returning() to get inserted/updated rows
- Generate migrations, don't push to production
References