Skip to main content
skill CX Codex

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.

Install into Claude Code · Skills CLI (Vercel) All methods and platforms ↓
npx skills add Mindrally/skills --skill drizzle-orm -a claude-code
Open on GitHub

What 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-code

2Manually: put the files in place

~/.claude/skills/drizzle-orm/SKILL.md

Copy the whole skill folder; for one project use .claude/skills/drizzle-orm/

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

Similar skills

All skills for Data and databases →

From the same set168

matplotlib-best-practices A skill that feeds your agent Matplotlib conventions: plot types, axis labels, colorblind-safe palettes, PDF and PNG export. CX Codex +1 1,000installs mqtt-development A skill that hands your agent a rulebook for MQTT work: topic design, QoS levels, sessions, LWT and transport security. Useful when you build IoT or real-time messaging code. CX Codex +1 1,000installs python-testing Guides the coding assistant to write Python tests using pytest, TDD principles, hypothesis, and mocks. Useful for unit and integration testing. CX Codex +1 1,000installs vuejs-typescript-best-practices Hands your agent a rulebook for Vue.js and TypeScript: Composition API, Pinia, VueUse, Tailwind, plus Vite build tuning. Useful once a Vue codebase outgrows one person's style. CX Codex +1 1,000installs elasticsearch-best-practices Provides rules and code patterns for Elasticsearch index mappings, shard planning, and search query optimization directly to your agent. CX Codex +1 999installs numpy-best-practices A skill that gives your agent NumPy ground rules: vectorize instead of looping, pick dtypes, watch memory, use np.linalg. Useful whenever Python code crunches numbers. CX Codex +1 997installs python This skill tells your coding agent how to write Python: PEP 8, type hints, early returns, async for I/O. Useful when the agent edits or generates code in a Python repo. CX Codex +1 983installs nlp-natural-language-processing Technical guidelines for natural language processing tasks using transformers, spaCy, and NLTK. Guides agents on proper tokenization, entity extraction, and vector search. CX Codex +1 980installs

Project sponsors

Companies that keep this analytics open
Gold sponsor

Impressions, clicks and code copies — open statistics. We keep the numbers public for the same reason NeBlask itself is public. Become a sponsor