Builds type-safe database applications with Drizzle ORM including schema definition, queries, relations, and migrations. Use when working with databases in TypeScript, defining schemas, writing type-safe queries, or managing migrations.
Builds type-safe database applications with Drizzle ORM including schema definition, queries, relations, and migrations. Use when working with databases in TypeScript, defining schemas, writing type-safe queries, or managing migrations.
/plugin marketplace add mgd34msu/goodvibes-plugin/plugin install goodvibes@goodvibes-marketThis skill inherits all available tools. When active, it can use any tool Claude has access to.
Lightweight, type-safe TypeScript ORM with zero dependencies and SQL-first approach.
Install:
# PostgreSQL
npm install drizzle-orm postgres
npm install -D drizzle-kit
# MySQL
npm install drizzle-orm mysql2
npm install -D drizzle-kit
# SQLite
npm install drizzle-orm better-sqlite3
npm install -D drizzle-kit @types/better-sqlite3
Project structure:
src/
db/
index.ts # Database connection
schema.ts # Table definitions
relations.ts # Relation definitions
drizzle.config.ts # Drizzle Kit config
// db/index.ts
import { drizzle } from 'drizzle-orm/postgres-js';
import postgres from 'postgres';
import * as schema from './schema';
const connectionString = process.env.DATABASE_URL!;
const client = postgres(connectionString);
export const db = drizzle(client, { schema });
// db/index.ts
import { drizzle } from 'drizzle-orm/mysql2';
import mysql from 'mysql2/promise';
import * as schema from './schema';
const connection = await mysql.createConnection({
host: 'localhost',
user: 'root',
database: 'mydb',
});
export const db = drizzle(connection, { schema });
// db/index.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 });
import { drizzle } from 'drizzle-orm/libsql';
import { createClient } from '@libsql/client';
import * as schema from './schema';
const client = createClient({
url: process.env.TURSO_DATABASE_URL!,
authToken: process.env.TURSO_AUTH_TOKEN,
});
export const db = drizzle(client, { schema });
// db/schema.ts
import {
pgTable,
serial,
varchar,
text,
integer,
boolean,
timestamp,
uuid,
jsonb,
} from 'drizzle-orm/pg-core';
export const users = pgTable('users', {
id: serial('id').primaryKey(),
email: varchar('email', { length: 255 }).notNull().unique(),
name: varchar('name', { length: 100 }),
bio: text('bio'),
age: integer('age'),
isActive: boolean('is_active').default(true),
createdAt: timestamp('created_at').defaultNow(),
updatedAt: timestamp('updated_at').defaultNow(),
});
export const posts = pgTable('posts', {
id: uuid('id').primaryKey().defaultRandom(),
title: varchar('title', { length: 255 }).notNull(),
content: text('content'),
published: boolean('published').default(false),
authorId: integer('author_id')
.notNull()
.references(() => users.id),
metadata: jsonb('metadata'),
createdAt: timestamp('created_at').defaultNow(),
});
export const comments = pgTable('comments', {
id: serial('id').primaryKey(),
content: text('content').notNull(),
postId: uuid('post_id')
.notNull()
.references(() => posts.id, { onDelete: 'cascade' }),
authorId: integer('author_id')
.notNull()
.references(() => users.id),
createdAt: timestamp('created_at').defaultNow(),
});
import {
mysqlTable,
serial,
varchar,
text,
int,
boolean,
timestamp,
json,
} from 'drizzle-orm/mysql-core';
export const users = mysqlTable('users', {
id: serial('id').primaryKey(),
email: varchar('email', { length: 255 }).notNull().unique(),
name: varchar('name', { length: 100 }),
createdAt: timestamp('created_at').defaultNow(),
});
import {
sqliteTable,
integer,
text,
} from 'drizzle-orm/sqlite-core';
export const users = sqliteTable('users', {
id: integer('id').primaryKey({ autoIncrement: true }),
email: text('email').notNull().unique(),
name: text('name'),
createdAt: integer('created_at', { mode: 'timestamp' })
.$defaultFn(() => new Date()),
});
// Infer types from schema
export type User = typeof users.$inferSelect;
export type NewUser = typeof users.$inferInsert;
export type Post = typeof posts.$inferSelect;
export type NewPost = typeof posts.$inferInsert;
// db/relations.ts
import { relations } from 'drizzle-orm';
import { users, posts, comments } from './schema';
export const usersRelations = relations(users, ({ many }) => ({
posts: many(posts),
comments: many(comments),
}));
export const postsRelations = relations(posts, ({ one, many }) => ({
author: one(users, {
fields: [posts.authorId],
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.authorId],
references: [users.id],
}),
}));
// Schema
export const usersToGroups = pgTable('users_to_groups', {
userId: integer('user_id')
.notNull()
.references(() => users.id),
groupId: integer('group_id')
.notNull()
.references(() => groups.id),
}, (t) => ({
pk: primaryKey({ columns: [t.userId, t.groupId] }),
}));
// Relations
export const usersRelations = relations(users, ({ many }) => ({
usersToGroups: many(usersToGroups),
}));
export const groupsRelations = relations(groups, ({ many }) => ({
usersToGroups: many(usersToGroups),
}));
export const usersToGroupsRelations = relations(usersToGroups, ({ one }) => ({
user: one(users, {
fields: [usersToGroups.userId],
references: [users.id],
}),
group: one(groups, {
fields: [usersToGroups.groupId],
references: [groups.id],
}),
}));
import { eq, lt, gte, ne, and, or, like, ilike, between, inArray } from 'drizzle-orm';
import { db } from './db';
import { users, posts } from './db/schema';
// Select all
const allUsers = await db.select().from(users);
// Select specific columns
const userNames = await db.select({
id: users.id,
name: users.name,
}).from(users);
// Where clause
const activeUsers = await db.select()
.from(users)
.where(eq(users.isActive, true));
// Multiple conditions
const filteredUsers = 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.name, 'John'),
eq(users.name, 'Jane'),
)
);
// Pattern matching
const matching = await db.select()
.from(users)
.where(like(users.email, '%@gmail.com'));
// Case-insensitive (PostgreSQL)
const matchingCI = await db.select()
.from(users)
.where(ilike(users.name, '%john%'));
// Range queries
const inRange = await db.select()
.from(users)
.where(between(users.age, 18, 65));
// IN operator
const specificUsers = await db.select()
.from(users)
.where(inArray(users.id, [1, 2, 3]));
import { eq } from 'drizzle-orm';
// Inner join
const postsWithAuthors = await db.select({
post: posts,
author: users,
})
.from(posts)
.innerJoin(users, eq(posts.authorId, users.id));
// Left join
const usersWithPosts = await db.select({
user: users,
post: posts,
})
.from(users)
.leftJoin(posts, eq(users.id, posts.authorId));
// Multiple joins
const fullData = await db.select()
.from(posts)
.innerJoin(users, eq(posts.authorId, users.id))
.leftJoin(comments, eq(posts.id, comments.postId));
import { asc, desc } from 'drizzle-orm';
// Order by
const orderedUsers = await db.select()
.from(users)
.orderBy(desc(users.createdAt));
// Multiple order columns
const sorted = await db.select()
.from(users)
.orderBy(asc(users.name), desc(users.createdAt));
// Pagination
const page = await db.select()
.from(users)
.limit(10)
.offset(20);
import { count, sum, avg, min, max } from 'drizzle-orm';
// Count
const userCount = await db.select({
count: count(),
}).from(users);
// Group by
const postsByAuthor = await db.select({
authorId: posts.authorId,
postCount: count(posts.id),
})
.from(posts)
.groupBy(posts.authorId);
// With having
const activeAuthors = await db.select({
authorId: posts.authorId,
postCount: count(posts.id),
})
.from(posts)
.groupBy(posts.authorId)
.having(({ postCount }) => gte(postCount, 5));
// Find many with relations
const usersWithPosts = await db.query.users.findMany({
with: {
posts: true,
},
});
// Nested relations
const postsWithDetails = await db.query.posts.findMany({
with: {
author: true,
comments: {
with: {
author: true,
},
},
},
});
// Find first
const user = await db.query.users.findFirst({
where: (users, { eq }) => eq(users.id, 1),
with: {
posts: true,
},
});
// Select specific columns
const partialPosts = await db.query.posts.findMany({
columns: {
id: true,
title: true,
},
with: {
author: {
columns: {
name: true,
},
},
},
});
// Filter nested relations
const recentPosts = await db.query.users.findMany({
with: {
posts: {
where: (posts, { eq }) => eq(posts.published, true),
orderBy: (posts, { desc }) => [desc(posts.createdAt)],
limit: 5,
},
},
});
// Single insert
await db.insert(users).values({
email: 'john@example.com',
name: 'John Doe',
});
// Insert with returning (PostgreSQL, SQLite)
const [newUser] = await db.insert(users)
.values({
email: 'jane@example.com',
name: 'Jane Doe',
})
.returning();
// Multiple insert
await db.insert(users).values([
{ email: 'user1@example.com', name: 'User 1' },
{ email: 'user2@example.com', name: 'User 2' },
]);
// Upsert (on conflict)
await db.insert(users)
.values({
email: 'john@example.com',
name: 'John Updated',
})
.onConflictDoUpdate({
target: users.email,
set: { name: 'John Updated' },
});
// On conflict do nothing
await db.insert(users)
.values({ email: 'john@example.com', name: 'John' })
.onConflictDoNothing({ target: users.email });
// Update with where
await db.update(users)
.set({ name: 'John Smith' })
.where(eq(users.id, 1));
// Update with returning
const [updated] = await db.update(users)
.set({ isActive: false })
.where(eq(users.id, 1))
.returning();
// Increment value
await db.update(posts)
.set({ viewCount: sql`${posts.viewCount} + 1` })
.where(eq(posts.id, 1));
// Delete with where
await db.delete(users).where(eq(users.id, 1));
// Delete with returning
const [deleted] = await db.delete(users)
.where(eq(users.id, 1))
.returning();
// Delete all (be careful!)
await db.delete(users);
// Basic transaction
await db.transaction(async (tx) => {
const [user] = await tx.insert(users)
.values({ email: 'new@example.com', name: 'New User' })
.returning();
await tx.insert(posts).values({
title: 'First Post',
authorId: user.id,
});
});
// With rollback
await db.transaction(async (tx) => {
await tx.insert(users).values({ email: 'test@example.com' });
// Rollback on condition
if (someCondition) {
tx.rollback();
}
await tx.insert(posts).values({ title: 'Post', authorId: 1 });
});
// Nested transactions (savepoints)
await db.transaction(async (tx) => {
await tx.insert(users).values({ email: 'outer@example.com' });
await tx.transaction(async (tx2) => {
await tx2.insert(posts).values({ title: 'Nested', authorId: 1 });
});
});
// drizzle.config.ts
import { defineConfig } from 'drizzle-kit';
export default defineConfig({
schema: './src/db/schema.ts',
out: './drizzle',
dialect: 'postgresql',
dbCredentials: {
url: process.env.DATABASE_URL!,
},
});
# Generate migration
npx drizzle-kit generate
# Apply migrations
npx drizzle-kit migrate
# Push schema (dev, no migration files)
npx drizzle-kit push
# Pull existing database schema
npx drizzle-kit pull
# Open Drizzle Studio
npx drizzle-kit studio
import { sql } from 'drizzle-orm';
// Raw query
const result = await db.execute(sql`SELECT * FROM users WHERE id = ${userId}`);
// In select
const users = await db.select({
id: users.id,
fullName: sql<string>`${users.firstName} || ' ' || ${users.lastName}`,
}).from(users);
// Typed raw
const count = await db.execute<{ count: number }>(
sql`SELECT COUNT(*) as count FROM users`
);
import { placeholder } from 'drizzle-orm';
// Prepare statement
const prepared = db.select()
.from(users)
.where(eq(users.id, placeholder('id')))
.prepare('get_user');
// Execute with values
const user = await prepared.execute({ id: 1 });
// Relational prepared
const preparedWithPosts = db.query.users.findFirst({
where: (users, { eq }) => eq(users.id, placeholder('id')),
with: { posts: true },
}).prepare('user_with_posts');
const result = await preparedWithPosts.execute({ id: 1 });
| Mistake | Fix |
|---|---|
| Missing schema in drizzle() | Pass { schema } to enable query API |
| Not exporting relations | Export from schema file |
| Using select() for relations | Use db.query for relational queries |
| Raw SQL without typing | Add type parameter to sql`` |
| Forgetting returning() | Add .returning() for inserted data |
This skill should be used when the user asks to "create a slash command", "add a command", "write a custom command", "define command arguments", "use command frontmatter", "organize commands", "create command with file references", "interactive command", "use AskUserQuestion in command", or needs guidance on slash command structure, YAML frontmatter fields, dynamic arguments, bash execution in commands, user interaction patterns, or command development best practices for Claude Code.
This skill should be used when the user asks to "create an agent", "add an agent", "write a subagent", "agent frontmatter", "when to use description", "agent examples", "agent tools", "agent colors", "autonomous agent", or needs guidance on agent structure, system prompts, triggering conditions, or agent development best practices for Claude Code plugins.
This skill should be used when the user asks to "create a hook", "add a PreToolUse/PostToolUse/Stop hook", "validate tool use", "implement prompt-based hooks", "use ${CLAUDE_PLUGIN_ROOT}", "set up event-driven automation", "block dangerous commands", or mentions hook events (PreToolUse, PostToolUse, Stop, SubagentStop, SessionStart, SessionEnd, UserPromptSubmit, PreCompact, Notification). Provides comprehensive guidance for creating and implementing Claude Code plugin hooks with focus on advanced prompt-based hooks API.