Expert guide for Drizzle ORM best practices, including schema definitions, queries, mutations, transactions, migrations, and performance optimization. Use when working with Drizzle ORM, database schemas, queries, or migrations.
Scanned 6/5/2026
Install via CLI
openskills install pedronauck/skills---
name: drizzle-orm
description: Expert guide for Drizzle ORM best practices, including schema definitions, queries, mutations, transactions, migrations, and performance optimization. Use when working with Drizzle ORM, database schemas, queries, or migrations.
allowed-tools: Read, Grep, Glob
---
# Drizzle ORM Developer Guide
This skill provides guidelines, patterns, and best practices for working with Drizzle ORM in this project.
## Quick Start
For detailed development guidelines, patterns, and code examples, refer to [references/patterns.md](references/patterns.md).
## Core Philosophy
- **Type-Safe by Default**: Leverage TypeScript and Drizzle's type inference to catch database errors at compile time
- **Performance First**: Use prepared statements and query optimization techniques
- **SQL Transparency**: Keep SQL queries readable and avoid over-abstraction
- **Minimal Overhead**: Drizzle is a thin layer - use it accordingly without unnecessary complexity
## Common Tasks
### Schema Definition
Organize schemas by domain in separate files. Use fluent constraint chaining and add indexes for frequently queried columns.
```typescript
export const users = pgTable('users', {
id: uuid('id').primaryKey().defaultRandom(),
email: varchar('email', { length: 255 }).notNull().unique(),
createdAt: timestamp('created_at').defaultNow().notNull(),
});
```
### Type Exports
Always export table types for use in your application:
```typescript
export type User = typeof users.$inferSelect;
export type NewUser = typeof users.$inferInsert;
```
### Prepared Statements
Use prepared statements for frequently executed queries for extreme performance benefits:
```typescript
export const getUserById = db
.select()
.from(users)
.where(eq(users.id, sql.placeholder('id')))
.prepare();
// Usage
const user = await getUserById.execute({ id: userId });
```
### Transactions
Use transactions for multi-step operations to maintain data consistency:
```typescript
return db.transaction(async (tx) => {
const [user] = await tx.insert(users).values(userData).returning();
const [profile] = await tx.insert(profiles).values({ userId: user.id, ...profileData }).returning();
return { user, profile };
});
```
## Migration Commands
**CRITICAL**: Always use package scripts for migrations. Never call `drizzle-kit` directly.
- Backend: `pnpm run db:generate` (generates migration)
- Backend: `pnpm run db:migrate` (applies migrations)
- Landing: `pnpm run db:generate` (generates migration)
- Landing: `pnpm run db:migrate` (applies migrations)
## Code Organization
```
db/
index.ts (Drizzle client initialization)
schema/
users.ts (User table & relations)
posts.ts (Post table & relations)
queries/
users.ts (User query functions)
posts.ts (Post query functions)
migrations/ (Auto-generated migration files)
```
## Common Pitfalls to Avoid
1. **Missing Indexes**: Always index columns used in WHERE, JOIN, and ORDER BY clauses
2. **Unbounded Queries**: Always use `limit()` and `offset()` for user-facing queries
3. **Unsafe Raw SQL**: Never concatenate user input into raw SQL - use `sql.placeholder()`
4. **N+1 Queries**: Use eager loading with `with()` or batch queries
5. **Missing Transactions**: Wrap multi-step operations in transactions
## Validation Checklist
Before finishing a task involving Drizzle ORM:
- [ ] Check schema definitions have proper indexes for queried columns
- [ ] Verify prepared statements are used for repeated queries
- [ ] Ensure transactions wrap multi-step operations
- [ ] Use package scripts for migrations (never call drizzle-kit directly)
- [ ] Run type checks (`pnpm run typecheck`) and tests (`pnpm run test`)
For detailed rules and code examples, consult [references/patterns.md](references/patterns.md).
No comments yet. Be the first to comment!