Claude-skill-registry drizzle
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.
install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/drizzle" ~/.claude/skills/majiayu000-claude-skill-registry-drizzle && rm -rf "$T"
manifest:
skills/data/drizzle/SKILL.mdsource content
Drizzle ORM
Lightweight, type-safe TypeScript ORM with zero dependencies and SQL-first approach.
Quick Start
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
Database Connection
PostgreSQL
// 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 });
MySQL
// 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 });
SQLite
// 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 });
Turso (Edge SQLite)
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 });
Schema Definition
PostgreSQL Tables
// 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(), });
MySQL Tables
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(), });
SQLite Tables
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()), });
Type Inference
// 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;
Relations
// 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], }), }));
Many-to-Many
// 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], }), }));
Queries
Select
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]));
Joins
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));
Ordering and Pagination
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);
Aggregations
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));
Relational Queries
// 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, }, }, });
Insert
// 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
// 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
// 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);
Transactions
// 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 }); }); });
Migrations
Configuration
// 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!, }, });
Commands
# 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
Raw SQL
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` );
Prepared Statements
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 });
Best Practices
- Export types from schema - Use $inferSelect and $inferInsert
- Use relations for nested data - Single query, no N+1
- Prepare frequent queries - Better performance
- Use transactions for multi-step ops - Ensure consistency
- Leverage push for development - Faster iteration
Common Mistakes
| 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 |
Reference Files
- references/queries.md - Advanced query patterns
- references/migrations.md - Migration workflows
- references/relations.md - Complex relations