drizzle-orm — Claude Code skill: Drizzle ORM guidelines
A skill that gives your agent Drizzle ORM conventions: schema as code, inferred types, queries and migrations.
npx skills add Mindrally/skills --skill drizzle-orm -a claude-codeWhat drizzle-orm does
The skill covers how to write Drizzle ORM code: table definitions with pgTable, relations, indexes, connections to PostgreSQL, SQLite and Turso/LibSQL, plus select, insert, update, delete and transaction patterns. Migrations run through drizzle-kit, and types come from the schema via InferSelectModel and InferInsertModel.
Everything lives in one SKILL.md file with TypeScript examples. Your agent gets ready-made snippets: a drizzle.config.ts, bulk inserts, upserts with onConflictDoUpdate, nested relations in db.query. It also advises turning on strict and strictNullChecks in tsconfig.json — without strict mode the inferred types lose much of their value.
This is for projects already on Drizzle. If you run Prisma or TypeORM, the examples will only confuse the agent, because it starts blending syntaxes. The text stops at the performance section, so do not expect query tuning advice here.
What it helps with
- define tables and relations in a Drizzle schema
- connect a database via node-postgres, better-sqlite3 or libsql
- build a query with and, or and like conditions
- upsert a row on an email conflict
- infer TypeScript types from tables
Example prompts for the agent
- Write users and posts tables in Drizzle ORM with relations and indexes
- Upsert a user by email with onConflictDoUpdate
- Set up drizzle.config.ts for PostgreSQL and generate a migration
How to install drizzle-orm
1Skills CLI (Vercel)
npx skills add Mindrally/skills --skill drizzle-orm -a claude-code2Manually: put the files in place
~/.claude/skills/drizzle-orm/SKILL.mdCopy the whole skill folder; for one project use .claude/skills/drizzle-orm/
1Skills CLI (Vercel)
npx skills add Mindrally/skills --skill drizzle-orm -a codex2Manually: put the files in place
.agents/skills/drizzle-orm/SKILL.mdFor all projects use ~/.agents/skills/
1Skills CLI (Vercel)
npx skills add Mindrally/skills --skill drizzle-orm -a cursor2Manually: put the files in place
.cursor/skills/drizzle-orm/SKILL.mdCursor also reads .agents/skills/ and .claude/skills/
1GitHub CLI
gh skill install Mindrally/skills drizzle-orm2Manually: put the files in place
.github/skills/drizzle-orm/SKILL.md1Skills CLI (Vercel)
npx skills add Mindrally/skills --skill drizzle-orm -a gemini-cli2Platform CLI
gemini skills install https://github.com/Mindrally/skills --path drizzle-ormManually: put the files in place
.devin/skills/drizzle-orm/SKILL.mdThe legacy .windsurf/skills/ path still works
Skills CLI (Vercel)
npx skills add Mindrally/skills --skill drizzle-orm -a opencodeClaude.ai
Zip the folder and upload it in Settings → Capabilities → Skills
Step by step, with commands for each platform: How to install skills in Claude Code: five ways
Questions about drizzle-orm
How do I install drizzle-orm in Claude?
Run npx skills add Mindrally/skills --skill drizzle-orm -a claude-code. The skill lands in ~/.claude/skills/drizzle-orm/SKILL.md.
What does the drizzle-orm skill do?
It hands your agent Drizzle ORM conventions and code samples: schema, relations, indexes, queries, transactions, migrations and type inference.
Which databases does the skill cover?
The examples use PostgreSQL through node-postgres, SQLite through better-sqlite3 and Turso/LibSQL. You pick the dialect in drizzle.config.ts.
Source text
SKILL.md · Apache-2.0
---
name: drizzle-orm
description: Guidelines for developing with Drizzle ORM, a lightweight type-safe TypeScript ORM with SQL-like syntax
metadata:
maintainer: Mindrally
source: https://github.com/Mindrally/skills
---
# Drizzle ORM Development Guidelines
You are an expert in Drizzle ORM, TypeScript, and SQL database design with a focus on type safety and performance.
## Core Principles
- Drizzle embraces SQL - if you know SQL, you know Drizzle
- Schema-as-code serves as the single source of truth
- Type safety is enforced at compile time, catching errors before runtime
- Lightweight with minimal runtime overhead (~7.4kb min+gzip)
- Serverless-ready: works with Node.js, Bun, Deno, Cloudflare Workers
## Schema Design
### Basic Table Definition
```typescript
import { pgTable, serial, text, varchar, timestamp, boolean, integer } from "drizzle-orm/pg-core";
export const users = pgTable("users", {
id: serial("id").primaryKey(),
email: varchar("email", { length: 255 }).notNull().unique(),
name: text("name"),
isActive: boolean("is_active").default(true),
createdAt: timestamp("created_at").defaultNow(),
updatedAt: timestamp("updated_at").defaultNow(),
});
export const posts = pgTable("posts", {
id: serial("id").primaryKey(),
title: varchar("title", { length: 255 }).notNull(),
content: text("content"),
authorId: integer("author_id").references(() => users.id),
publishedAt: timestamp("published_at"),
createdAt: timestamp("created_at").defaultNow(),
});
```
### Schema Organization
You can organize schemas in multiple ways:
```typescript
// Option 1: Single schema.ts file (recommended for smaller projects)
// src/db/schema.ts
// Option 2: Split by domain (recommended for larger projects)
// src/db/schema/users.ts
// src/db/schema/posts.ts
// src/db/schema/index.ts (re-exports all)
```
### Naming Conventions
Use the `casing` option for automatic camelCase to snake_case mapping:
```typescript
import { drizzle } from "drizzle-orm/node-postgres";
const db = drizzle(pool, {
casing: "snake_case", // Automatically maps camelCase to snake_case
});
```
### Defining Relations
```typescript
import { relations } from "drizzle-orm";
export const usersRelations = relations(users, ({ many }) => ({
posts: many(posts),
}));
export const postsRelations = relations(posts, ({ one }) => ({
author: one(users, {
fields: [posts.authorId],
references: [users.id],
}),
}));
```
### Adding Indexes
```typescript
import { pgTable, serial, varchar, index, uniqueIndex } from "drizzle-orm/pg-core";
export const users = pgTable(
"users",
{
id: serial("id").primaryKey(),
email: varchar("email", { length: 255 }).notNull(),
name: varchar("name", { length: 255 }),
},
(table) => [
uniqueIndex("email_idx").on(table.email),
index("name_idx").on(table.name),
]
);
```
## Database Connection
### PostgreSQL with node-postgres
```typescript
import { drizzle } from "drizzle-orm/node-postgres";
import { Pool } from "pg";
import * as schema from "./schema";
const pool = new Pool({
connectionString: process.env.DATABASE_URL,
});
export const db = drizzle(pool, { schema });
```
### SQLite with better-sqlite3
```typescript
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/LibSQL
```typescript
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 });
```
## Query Patterns
### Select Queries
```typescript
// Select all columns
const allUsers = await db.select().from(users);
// Select specific columns
const userEmails = await db.select({ email: users.email }).from(users);
// With conditions
import { eq, and, or, gt, like } from "drizzle-orm";
const activeUsers = await db
.select()
.from(users)
.where(eq(users.isActive, true));
const filteredUsers = await db
.select()
.from(users)
.where(
and(
eq(users.isActive, true),
like(users.email, "%@example.com")
)
);
```
### Relational Queries
```typescript
// Query with relations (requires schema with relations defined)
const usersWithPosts = await db.query.users.findMany({
with: {
posts: true,
},
});
// Nested relations
const postsWithAuthor = await db.query.posts.findMany({
with: {
author: {
columns: {
id: true,
name: true,
},
},
},
});
```
### Insert Operations
```typescript
// Single insert
const newUser = await db
.insert(users)
.values({
email: "[email protected]",
name: "John Doe",
})
.returning();
// Bulk insert
await db.insert(users).values([
{ email: "[email protected]", name: "User 1" },
{ email: "[email protected]", name: "User 2" },
]);
// Upsert (insert or update on conflict)
await db
.insert(users)
.values({ email: "[email protected]", name: "John" })
.onConflictDoUpdate({
target: users.email,
set: { name: "John Updated" },
});
```
### Update Operations
```typescript
await db
.update(users)
.set({ name: "Jane Doe", updatedAt: new Date() })
.where(eq(users.id, 1));
```
### Delete Operations
```typescript
await db.delete(users).where(eq(users.id, 1));
```
### Transactions
```typescript
await db.transaction(async (tx) => {
const [user] = await tx
.insert(users)
.values({ email: "[email protected]", name: "User" })
.returning();
await tx.insert(posts).values({
title: "First Post",
authorId: user.id,
});
});
```
## Migrations
### Generate Migrations
```bash
# Generate migration based on schema changes
npx drizzle-kit generate
# Apply migrations to database
npx drizzle-kit migrate
# Push schema directly (development only)
npx drizzle-kit push
```
### Migration Configuration
```typescript
// 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!,
},
});
```
## Type Safety Best Practices
### Infer Types from Schema
```typescript
import { InferSelectModel, InferInsertModel } from "drizzle-orm";
// Infer types from table definitions
export type User = InferSelectModel<typeof users>;
export type NewUser = InferInsertModel<typeof users>;
// Use in application code
function createUser(data: NewUser): Promise<User> {
return db.insert(users).values(data).returning().then((r) => r[0]);
}
```
### Strict TypeScript Configuration
Ensure strict mode is enabled in tsconfig.json:
```json
{
"compilerOptions": {
"strict": true,
"strictNullChecks": true
}
}
```
## Performance Best Practices
### Use Indexes Appropriately
Always add indexes for columns used in WHERE clauses and JOINs:
```typescript
export const orders = pgTable(
"orders",
{
id: serial("id").primaryKey(),
userId: integer("user_id").notNull(),
status: varchar("status", { length: 50 }).notNull(),
createdAt: timestamp("created_at").defaultNow(),
},
(table) => [
index("user_id_idx").on(table.userId),
index("status_idx").on(table.status),
index("created_at_idx").on(table.createdAt),
]
);
```
### Select Only Needed Columns
```typescript
// Bad: Fetches all columns
const users = await db.select().from(users);
// Good: Fetches only needed columns
const userNames = await db
.select({ id: users.id, name: users.name })
.from(users);
```
### Use Proper Pagination
```typescript
const page = 1;
const pageSize = 20;
const paginatedUsers = await db
.select()
.from(users)
.limit(pageSize)
.offset((page - 1) * pageSize)
.orderBy(users.createdAt);
```
### Avoid N+1 Queries
```typescript
// Bad: N+1 query pattern
const users = await db.select().from(users);
for (const user of users) {
const posts = await db.select().from(posts).where(eq(posts.authorId, user.id));
}
// Good: Use relational queries or joins
const usersWithPosts = await db.query.users.findMany({
with: { posts: true },
});
```
## Common Mistakes to Avoid
1. **Not defining indexes** - Always add indexes for frequently queried columns
2. **Fetching too much data** - Select only the columns you need
3. **Missing foreign key constraints** - Define proper relationships in schema
4. **Manual migration modifications** - Let drizzle-kit manage migration history
5. **Not using transactions** - Wrap related operations in transactions for data integrity
© Mindrally · Apache-2.0 · Mindrally/skills