drizzle-orm
Expert guidance for Drizzle ORM including schema definition, queries, relations, migrations, TypeScript integration with SQLite/PostgreSQL, and Drizzle Studio. Use this when working with type-safe database operations, schema management, or ORM queries.
About drizzle-orm
drizzle-orm is a Claude AI skill developed by oriolrius. Expert guidance for Drizzle ORM including schema definition, queries, relations, migrations, TypeScript integration with SQLite/PostgreSQL, and Drizzle Studio. Use this when working with type-safe database operations, schema management, or ORM queries. This powerful Claude Code plugin helps developers automate workflows and enhance productivity with intelligent AI assistance.
Why use drizzle-orm? With 0 stars on GitHub, this skill has been trusted by developers worldwide. Install this Claude skill instantly to enhance your development workflow with AI-powered automation.
| name | Drizzle ORM |
| description | Expert guidance for Drizzle ORM including schema definition, queries, relations, migrations, TypeScript integration with SQLite/PostgreSQL, and Drizzle Studio. Use this when working with type-safe database operations, schema management, or ORM queries. |
Drizzle ORM
Expert assistance with Drizzle ORM - TypeScript ORM for SQL databases.
Overview
Drizzle ORM is a lightweight TypeScript ORM:
- Type-Safe: Full TypeScript type inference
- SQL-Like: Familiar SQL syntax, not a new query language
- Performant: Zero overhead, generates efficient SQL
- Multiple Databases: PostgreSQL, MySQL, SQLite support
- Migrations: Built-in migration system
- Drizzle Studio: Visual database browser
Installation
# Core packages npm install drizzle-orm npm install --save-dev drizzle-kit # Database driver (choose one) npm install better-sqlite3 # For SQLite npm install @types/better-sqlite3 --save-dev # Or for PostgreSQL npm install postgres # For PostgreSQL npm install pg # Alternative PostgreSQL driver
Quick Start (SQLite)
1. Define Schema
// src/db/schema.ts import { sqliteTable, text, integer } from 'drizzle-orm/sqlite-core'; export const users = sqliteTable('users', { id: text('id').primaryKey(), name: text('name').notNull(), email: text('email').notNull().unique(), createdAt: integer('created_at', { mode: 'timestamp' }).notNull().$defaultFn(() => new Date()), }); export const posts = sqliteTable('posts', { id: text('id').primaryKey(), title: text('title').notNull(), content: text('content').notNull(), userId: text('user_id').notNull().references(() => users.id, { onDelete: 'cascade' }), createdAt: integer('created_at', { mode: 'timestamp' }).notNull().$defaultFn(() => new Date()), });
2. Create Database Client
// src/db/client.ts import { drizzle } from 'drizzle-orm/better-sqlite3'; import Database from 'better-sqlite3'; import * as schema from './schema'; const sqlite = new Database('sqlite.db'); export const db = drizzle(sqlite, { schema });
3. Use in Application
import { db } from './db/client'; import { users, posts } from './db/schema'; import { eq } from 'drizzle-orm'; // Insert const newUser = await db.insert(users).values({ id: '1', name: 'John Doe', email: 'john@example.com', }).returning(); // Query const allUsers = await db.select().from(users); const user = await db.select().from(users).where(eq(users.id, '1')); // Update await db.update(users) .set({ name: 'Jane Doe' }) .where(eq(users.id, '1')); // Delete await db.delete(users).where(eq(users.id, '1'));
Schema Definition
Column Types (SQLite)
import { sqliteTable, text, integer, real, blob } from 'drizzle-orm/sqlite-core'; export const examples = sqliteTable('examples', { // Text id: text('id').primaryKey(), name: text('name').notNull(), description: text('description'), // Integer age: integer('age'), count: integer('count').default(0), // Boolean (stored as integer 0/1) isActive: integer('is_active', { mode: 'boolean' }).default(true), // Timestamp (stored as integer unix epoch) createdAt: integer('created_at', { mode: 'timestamp' }), updatedAt: integer('updated_at', { mode: 'timestamp_ms' }), // milliseconds // Real (floating point) price: real('price'), // Blob (binary data) data: blob('data', { mode: 'buffer' }), // JSON (stored as text) metadata: text('metadata', { mode: 'json' }).$type<{ key: string; value: number }>(), });
Constraints
import { sqliteTable, text, integer, primaryKey, unique index } from 'drizzle-orm/sqlite-core'; export const users = sqliteTable('users', { id: text('id').primaryKey(), email: text('email').notNull().unique(), // Unique constraint name: text('name').notNull(), // Not null age: integer('age').default(18), // Default value }, (table) => ({ // Composite unique constraint emailNameUnique: unique().on(table.email, table.name), // Index emailIdx: index('email_idx').on(table.email), // Composite index nameAgeIdx: index('name_age_idx').on(table.name, table.age), })); // Composite primary key export const userRoles = sqliteTable('user_roles', { userId: text('user_id').notNull(), roleId: text('role_id').notNull(), }, (table) => ({ pk: primaryKey({ columns: [table.userId, table.roleId] }), }));
Check Constraints
import { sql } from 'drizzle-orm'; import { sqliteTable, text, integer, check } from 'drizzle-orm/sqlite-core'; export const certificates = sqliteTable('certificates', { id: text('id').primaryKey(), status: text('status').notNull(), serialNumber: text('serial_number').notNull(), }, (table) => ({ // Check constraint statusCheck: check('status_check', sql`${table.status} IN ('active', 'revoked', 'expired')`), }));
Foreign Keys
export const posts = sqliteTable('posts', { id: text('id').primaryKey(), userId: text('user_id') .notNull() .references(() => users.id, { onDelete: 'cascade', // Delete posts when user is deleted onUpdate: 'cascade', // Update posts when user id changes }), title: text('title').notNull(), }); // Self-referencing foreign key export const categories = sqliteTable('categories', { id: text('id').primaryKey(), name: text('name').notNull(), parentId: text('parent_id').references((): AnyPgColumn => categories.id), });
Default Values
import { sql } from 'drizzle-orm'; export const users = sqliteTable('users', { id: text('id').primaryKey(), name: text('name').notNull(), // SQL default createdAt: integer('created_at').default(sql`(unixepoch())`), // TypeScript default function id: text('id').$defaultFn(() => crypto.randomUUID()), createdAt: integer('created_at', { mode: 'timestamp' }).$defaultFn(() => new Date()), });
Queries
Select
import { eq, and, or, gt, gte, lt, lte, like, inArray } from 'drizzle-orm'; // Select all columns const allUsers = await db.select().from(users); // Select specific columns const names = await db.select({ id: users.id, name: users.name, }).from(users); // Where clauses const user = await db.select().from(users).where(eq(users.id, '1')); // Multiple conditions const activeAdults = await db.select().from(users).where( and( eq(users.isActive, true), gte(users.age, 18) ) ); // Or conditions const results = await db.select().from(users).where( or( eq(users.role, 'admin'), eq(users.role, 'moderator') ) ); // Like operator const johns = await db.select().from(users).where(like(users.name, '%John%')); // In array const specificUsers = await db.select().from(users).where( inArray(users.id, ['1', '2', '3']) ); // Comparison operators const adults = await db.select().from(users).where(gte(users.age, 18)); const minors = await db.select().from(users).where(lt(users.age, 18)); // Order by const sorted = await db.select().from(users).orderBy(users.name); const descending = await db.select().from(users).orderBy(desc(users.createdAt)); // Limit and offset const paginated = await db.select().from(users).limit(10).offset(20); // Get single result const user = await db.select().from(users).where(eq(users.id, '1')).get();
Joins
import { eq } from 'drizzle-orm'; // Inner join const usersWithPosts = await db .select() .from(users) .innerJoin(posts, eq(posts.userId, users.id)); // Left join const allUsersWithPosts = await db .select() .from(users) .leftJoin(posts, eq(posts.userId, users.id)); // Select specific columns from joined tables const results = await db .select({ userId: users.id, userName: users.name, postTitle: posts.title, }) .from(users) .leftJoin(posts, eq(posts.userId, users.id)); // Multiple joins const data = await db .select() .from(posts) .innerJoin(users, eq(posts.userId, users.id)) .leftJoin(comments, eq(comments.postId, posts.id));
Aggregations
import { count, sum, avg, min, max } from 'drizzle-orm'; // Count const userCount = await db.select({ count: count() }).from(users); // Count with condition const activeCount = await db .select({ count: count() }) .from(users) .where(eq(users.isActive, true)); // Group by const postsByUser = await db .select({ userId: posts.userId, postCount: count(), }) .from(posts) .groupBy(posts.userId); // Multiple aggregations const stats = await db .select({ total: count(), avgAge: avg(users.age), minAge: min(users.age), maxAge: max(users.age), }) .from(users); // Having clause const activeUsers = await db .select({ userId: posts.userId, postCount: count(), }) .from(posts) .groupBy(posts.userId) .having(({ postCount }) => gt(postCount, 5));
Subqueries
import { sql } from 'drizzle-orm'; // Subquery in WHERE const sq = db.select({ userId: posts.userId }).from(posts).groupBy(posts.userId); const activePosters = await db .select() .from(users) .where(inArray(users.id, sq)); // Subquery as column const usersWithPostCount = await db .select({ id: users.id, name: users.name, postCount: sql<number>`( SELECT COUNT(*) FROM ${posts} WHERE ${posts.userId} = ${users.id} )`, }) .from(users);
Insert
Single Insert
// Insert one await db.insert(users).values({ id: '1', name: 'John', email: 'john@example.com', }); // Insert with returning const newUser = await db.insert(users) .values({ id: '2', name: 'Jane', email: 'jane@example.com', }) .returning(); // Return specific columns const user = await db.insert(users) .values({ id: '3', name: 'Bob', email: 'bob@example.com' }) .returning({ id: users.id, name: users.name });
Bulk Insert
// Insert multiple await db.insert(users).values([ { id: '1', name: 'John', email: 'john@example.com' }, { id: '2', name: 'Jane', email: 'jane@example.com' }, { id: '3', name: 'Bob', email: 'bob@example.com' }, ]); // Bulk insert with returning const newUsers = await db.insert(users) .values([ { id: '4', name: 'Alice', email: 'alice@example.com' }, { id: '5', name: 'Charlie', email: 'charlie@example.com' }, ]) .returning();
Upsert (Insert or Update)
// SQLite 3.24+ (ON CONFLICT) await db.insert(users) .values({ id: '1', name: 'John', email: 'john@example.com' }) .onConflictDoUpdate({ target: users.id, set: { name: 'John Updated', email: 'john.updated@example.com' }, }); // Do nothing on conflict await db.insert(users) .values({ id: '1', name: 'John', email: 'john@example.com' }) .onConflictDoNothing(); // Update specific columns await db.insert(users) .values({ id: '1', name: 'John', email: 'john@example.com' }) .onConflictDoUpdate({ target: users.id, set: { updatedAt: sql`CURRENT_TIMESTAMP` }, });
Update
import { eq } from 'drizzle-orm'; // Update single row await db.update(users) .set({ name: 'John Updated' }) .where(eq(users.id, '1')); // Update multiple columns await db.update(users) .set({ name: 'Jane Smith', email: 'jane.smith@example.com', }) .where(eq(users.id, '2')); // Update with returning const updated = await db.update(users) .set({ name: 'Bob Updated' }) .where(eq(users.id, '3')) .returning(); // Update with SQL expression await db.update(users) .set({ age: sql`${users.age} + 1` }) .where(eq(users.id, '1')); // Conditional update await db.update(users) .set({ status: 'active' }) .where(and( eq(users.verified, true), gte(users.createdAt, new Date('2024-01-01')) ));
Delete
// Delete single row await db.delete(users).where(eq(users.id, '1')); // Delete multiple rows await db.delete(users).where(inArray(users.id, ['1', '2', '3'])); // Delete with condition await db.delete(users).where(lt(users.createdAt, new Date('2023-01-01'))); // Delete with returning const deleted = await db.delete(users) .where(eq(users.id, '1')) .returning(); // Delete all (be careful!) await db.delete(users);
Transactions
// Simple transaction await db.transaction(async (tx) => { await tx.insert(users).values({ id: '1', name: 'John', email: 'john@example.com' }); await tx.insert(posts).values({ id: '1', title: 'First Post', userId: '1' }); }); // Transaction with rollback try { await db.transaction(async (tx) => { await tx.insert(users).values({ id: '1', name: 'John', email: 'john@example.com' }); // This will cause transaction to rollback throw new Error('Rollback!'); await tx.insert(posts).values({ id: '1', title: 'Post', userId: '1' }); }); } catch (error) { console.error('Transaction failed:', error); } // Nested transactions await db.transaction(async (tx1) => { await tx1.insert(users).values({ id: '1', name: 'John', email: 'john@example.com' }); await tx1.transaction(async (tx2) => { await tx2.insert(posts).values({ id: '1', title: 'Post', userId: '1' }); }); });
Relations
Define Relations
// src/db/schema.ts import { relations } from 'drizzle-orm'; import { sqliteTable, text, integer } from 'drizzle-orm/sqlite-core'; export const users = sqliteTable('users', { id: text('id').primaryKey(), name: text('name').notNull(), }); export const posts = sqliteTable('posts', { id: text('id').primaryKey(), title: text('title').notNull(), userId: text('user_id').notNull().references(() => users.id), }); export const comments = sqliteTable('comments', { id: text('id').primaryKey(), content: text('content').notNull(), postId: text('post_id').notNull().references(() => posts.id), userId: text('user_id').notNull().references(() => users.id), }); // Define relations export const usersRelations = relations(users, ({ many }) => ({ posts: many(posts), comments: many(comments), })); export const postsRelations = relations(posts, ({ one, many }) => ({ author: one(users, { fields: [posts.userId], references: [users.id], }), comments: many(comments), })); export const commentsRelations = relations(comments, ({ one }) => ({ post: one(posts, { fields: [comments.postId], references: [posts.id], }), author: one(users, { fields: [comments.userId], references: [users.id], }), }));
Query with Relations
// Query with relations const usersWithPosts = await db.query.users.findMany({ with: { posts: true, }, }); // Nested relations const usersWithPostsAndComments = await db.query.users.findMany({ with: { posts: { with: { comments: true, }, }, }, }); // Filter relations const usersWithRecentPosts = await db.query.users.findMany({ with: { posts: { where: (posts, { gte }) => gte(posts.createdAt, new Date('2024-01-01')), }, }, }); // Select specific columns const data = await db.query.users.findMany({ columns: { id: true, name: true, }, with: { posts: { columns: { id: true, title: true, }, }, }, });
Migrations
Configuration
// drizzle.config.ts import type { Config } from 'drizzle-kit'; export default { schema: './src/db/schema.ts', out: './drizzle/migrations', driver: 'better-sqlite', dbCredentials: { url: './sqlite.db', }, } satisfies Config;
Generate Migrations
# Generate migration from schema changes npx drizzle-kit generate:sqlite # Custom migration name npx drizzle-kit generate:sqlite --name add_users_table # Generate with custom config npx drizzle-kit generate:sqlite --config drizzle.config.ts
Run Migrations
// src/db/migrate.ts import { drizzle } from 'drizzle-orm/better-sqlite3'; import { migrate } from 'drizzle-orm/better-sqlite3/migrator'; import Database from 'better-sqlite3'; const sqlite = new Database('sqlite.db'); const db = drizzle(sqlite); // Run migrations await migrate(db, { migrationsFolder: './drizzle/migrations' }); console.log('Migrations complete!'); sqlite.close();
Migration Files
-- drizzle/migrations/0001_add_users.sql CREATE TABLE `users` ( `id` text PRIMARY KEY NOT NULL, `name` text NOT NULL, `email` text NOT NULL UNIQUE, `created_at` integer NOT NULL ); CREATE INDEX `email_idx` ON `users` (`email`);
Drizzle Studio
# Start Drizzle Studio npx drizzle-kit studio # Custom port npx drizzle-kit studio --port 3333 # With custom config npx drizzle-kit studio --config drizzle.config.ts
Access at: http://localhost:4983
TypeScript Integration
Infer Types
import { InferSelectModel, InferInsertModel } from 'drizzle-orm'; import { users, posts } from './schema'; // Infer select model (what you get from queries) export type User = InferSelectModel<typeof users>; export type Post = InferSelectModel<typeof posts>; // Infer insert model (what you need to insert) export type InsertUser = InferInsertModel<typeof users>; export type InsertPost = InferInsertModel<typeof posts>; // Usage function createUser(user: InsertUser): Promise<User> { return db.insert(users).values(user).returning().get(); }
Typed Queries
// Type-safe query builder const query = db .select({ id: users.id, name: users.name, postCount: count(posts.id), }) .from(users) .leftJoin(posts, eq(posts.userId, users.id)) .groupBy(users.id); // Infer result type type QueryResult = Awaited<ReturnType<typeof query.execute>>;
Best Practices
- Use Transactions: Wrap multiple operations in transactions
- Define Relations: Use relations for easier queries
- Type Safety: Leverage TypeScript type inference
- Migrations: Use migration system, don't modify schema directly in production
- Indexes: Index frequently queried columns
- Prepared Statements: Drizzle automatically uses prepared statements
- Connection Management: Reuse database connection
- Studio: Use Drizzle Studio for visual database exploration
- Error Handling: Handle constraint violations
- Performance: Use
get()for single results instead ofall()[0]
Common Patterns
Repository Pattern
export class UserRepository { constructor(private db: ReturnType<typeof drizzle>) {} async findById(id: string): Promise<User | undefined> { return this.db.select().from(users).where(eq(users.id, id)).get(); } async findAll(): Promise<User[]> { return this.db.select().from(users); } async create(data: InsertUser): Promise<User> { return this.db.insert(users).values(data).returning().get(); } async update(id: string, data: Partial<InsertUser>): Promise<User | undefined> { return this.db.update(users).set(data).where(eq(users.id, id)).returning().get(); } async delete(id: string): Promise<boolean> { const result = await this.db.delete(users).where(eq(users.id, id)).returning(); return result.length > 0; } }
Resources
- Documentation: https://orm.drizzle.team/docs/overview
- GitHub: https://github.com/drizzle-team/drizzle-orm
- Examples: https://github.com/drizzle-team/drizzle-orm/tree/main/examples
- Drizzle Studio: https://orm.drizzle.team/drizzle-studio/overview

oriolrius
pki-manager-web
Download Skill Files
View Installation GuideDownload the complete skill directory including SKILL.md and all related files