ccheney

postgres-drizzle

Proactively apply when creating APIs, backends, or data models. Triggers on PostgreSQL, Postgres, Drizzle, database, schema, tables, columns, indexes, queries, migrations, ORM, relations, joins, transactions, SQL, drizzle-kit, connection pooling, N+1, JSONB, RLS. Use when writing database schemas, queries, migrations, or any database-related code. PostgreSQL and Drizzle ORM best practices.

ccheney 61 3 Updated 8mo ago

Resources

2
GitHub

Install

npx skillscat add ccheney/robust-skills/postgres-drizzle

Install via the SkillsCat registry.

SKILL.md

PostgreSQL + Drizzle ORM

Type-safe database applications with PostgreSQL 18 and Drizzle ORM.

Essential Commands

npx drizzle-kit generate   # Generate migration from schema changes
npx drizzle-kit migrate    # Apply pending migrations
npx drizzle-kit push       # Push schema directly (dev only!)
npx drizzle-kit studio     # Open database browser

Quick Decision Trees

"How do I model this relationship?"

Relationship type?
โ”œโ”€ One-to-many (user has posts)     โ†’ FK on "many" side + relations()
โ”œโ”€ Many-to-many (posts have tags)   โ†’ Junction table + relations()
โ”œโ”€ One-to-one (user has profile)    โ†’ FK with unique constraint
โ””โ”€ Self-referential (comments)      โ†’ FK to same table

"Why is my query slow?"

Slow query?
โ”œโ”€ Missing index on WHERE/JOIN columns  โ†’ Add index
โ”œโ”€ N+1 queries in loop                  โ†’ Use relational queries API
โ”œโ”€ Full table scan                      โ†’ EXPLAIN ANALYZE, add index
โ”œโ”€ Large result set                     โ†’ Add pagination (limit/offset)
โ””โ”€ Connection overhead                  โ†’ Enable connection pooling

"Which drizzle-kit command?"

What do I need?
โ”œโ”€ Schema changed, need SQL migration   โ†’ drizzle-kit generate
โ”œโ”€ Apply migrations to database         โ†’ drizzle-kit migrate
โ”œโ”€ Quick dev iteration (no migration)   โ†’ drizzle-kit push
โ””โ”€ Browse/edit data visually            โ†’ drizzle-kit studio

Directory Structure

src/db/
โ”œโ”€โ”€ schema/
โ”‚   โ”œโ”€โ”€ index.ts          # Re-export all tables
โ”‚   โ”œโ”€โ”€ users.ts          # Table + relations
โ”‚   โ””โ”€โ”€ posts.ts          # Table + relations
โ”œโ”€โ”€ db.ts                 # Connection with pooling
โ””โ”€โ”€ migrate.ts            # Migration runner
drizzle/
โ””โ”€โ”€ migrations/           # Generated SQL files
drizzle.config.ts         # drizzle-kit config

Schema Patterns

Basic Table with Timestamps

export const users = pgTable('users', {
  id: uuid('id').primaryKey().defaultRandom(),
  email: varchar('email', { length: 255 }).notNull().unique(),
  createdAt: timestamp('created_at').defaultNow().notNull(),
  updatedAt: timestamp('updated_at').defaultNow().notNull(),
});

Foreign Key with Index

export const posts = pgTable('posts', {
  id: uuid('id').primaryKey().defaultRandom(),
  userId: uuid('user_id').notNull().references(() => users.id),
  title: varchar('title', { length: 255 }).notNull(),
}, (table) => [
  index('posts_user_id_idx').on(table.userId), // ALWAYS index FKs
]);

Relations

export const usersRelations = relations(users, ({ many }) => ({
  posts: many(posts),
}));

export const postsRelations = relations(posts, ({ one }) => ({
  author: one(users, { fields: [posts.userId], references: [users.id] }),
}));

Query Patterns

Relational Query (Avoid N+1)

// โœ“ Single query with nested data
const usersWithPosts = await db.query.users.findMany({
  with: { posts: true },
});

Filtered Query

const activeUsers = await db
  .select()
  .from(users)
  .where(eq(users.status, 'active'));

Transaction

await db.transaction(async (tx) => {
  const [user] = await tx.insert(users).values({ email }).returning();
  await tx.insert(profiles).values({ userId: user.id });
});

Performance Checklist

Priority Check Impact
CRITICAL Index all foreign keys Prevents full table scans on JOINs
CRITICAL Use relational queries for nested data Avoids N+1
HIGH Connection pooling in production Reduces connection overhead
HIGH EXPLAIN ANALYZE slow queries Identifies missing indexes
MEDIUM Partial indexes for filtered subsets Smaller, faster indexes
MEDIUM UUIDv7 for PKs (PG18+) Better index locality

Anti-Patterns (CRITICAL)

Anti-Pattern Problem Fix
No FK index Slow JOINs, full scans Add index on every FK column
N+1 in loops Query per row Use with: relational queries
No pooling Connection per request Use @neondatabase/serverless or similar
push in prod Data loss risk Always use generate + migrate
Storing JSON as text No validation, bad queries Use jsonb() column type

Reference Documentation

File Purpose
references/SCHEMA.md Column types, constraints
references/QUERIES.md Operators, joins, aggregations
references/RELATIONS.md One-to-many, many-to-many
references/MIGRATIONS.md drizzle-kit workflows
references/POSTGRES.md PG18 features, RLS, partitioning
references/PERFORMANCE.md Indexing, optimization
references/CHEATSHEET.md Quick reference

Resources

Drizzle ORM

PostgreSQL