| name | drizzle-migrations |
| description | Manage database schema with Drizzle ORM and SQLite migrations. Use when adding tables, modifying columns, creating indexes, or running migrations. Activates for database schema changes, migration generation, and Drizzle query patterns. |
| allowed-tools | Read,Write,Edit,Bash(npm:*,npx:*) |
| category | Data & Analytics |
| tags | ["database","drizzle","migrations"] |
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