git clone https://github.com/NeverSight/learn-skills.dev
T=$(mktemp -d) && git clone --depth=1 https://github.com/NeverSight/learn-skills.dev "$T" && mkdir -p ~/.claude/skills && cp -r "$T/data/skills-md/agents-inc/skills/api-database-drizzle" ~/.claude/skills/neversight-learn-skills-dev-api-database-drizzle && rm -rf "$T"
data/skills-md/agents-inc/skills/api-database-drizzle/SKILL.mdDatabase with Drizzle ORM + Neon
Quick Guide: Use Drizzle ORM for type-safe queries, Neon serverless Postgres for edge-compatible connections. Schema-first design with automatic TypeScript types. Use RQB v2 with
and object-baseddefineRelations()syntax. Relational queries withwhereavoid N+1 problems. Use transactions for atomic operations..with()
<critical_requirements>
CRITICAL: Before Using This Skill
All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering,
, named constants)import type
(You MUST set
in Drizzle config to map camelCase JS to snake_case SQL)casing: 'snake_case'
(You MUST use
parameter (NOT tx
) inside transaction callbacks to ensure atomicity)db
(You MUST use
for relational queries to avoid N+1 problems - fetches all data in single SQL query).with()
(You MUST use
for RQB v2 - the old defineRelations()
per-table syntax is deprecated)relations()
</critical_requirements>
Detailed Resources:
- For code examples, see examples/ folder:
- core.md - Connection setup and schema definition (always loaded)
- queries.md - Relational queries and query builder
- relations-v2.md - RQB v2 with defineRelations() (NEW)
- transactions.md - Atomic operations
- migrations.md - Drizzle Kit workflow
- seeding.md - Development data population (includes drizzle-seed)
- For decision frameworks and anti-patterns, see reference.md
Auto-detection: drizzle-orm, @neondatabase/serverless, neon-http, db.query, db.transaction, drizzle-kit, pgTable, defineRelations, drizzle-seed
When to use:
- Serverless functions needing type-safe database queries
- Schema-first development with migrations
- Building server-rendered apps with API routes
When NOT to use:
- Simple apps using framework server actions directly (overhead not justified)
- Apps needing traditional TCP connection pooling only (use standard Postgres clients)
- Non-TypeScript projects (lose primary benefit of type safety)
- Edge functions requiring WebSocket connections (not supported in edge runtime)
<patterns>
Core Patterns
Pattern 1: Database Connection (Neon HTTP)
Configure Drizzle with Neon for serverless/edge compatibility. Key setup requirements:
export const db = drizzle(sql, { schema, casing: "snake_case", // Maps camelCase JS to snake_case SQL });
- Validate
before use (throw on missing)DATABASE_URL - Always set
to prevent field name mismatchescasing: "snake_case" - Use
for HTTP (edge-compatible) orneon()
for WebSocket (long queries)Pool
Full connection setup, WebSocket config, and Drizzle Kit config in examples/core.md.
Pattern 2: Schema Definition
Define tables with TypeScript types using Drizzle's schema builder:
export const companies = pgTable("companies", { id: uuid("id").primaryKey().defaultRandom(), name: varchar("name", { length: 255 }).notNull(), slug: varchar("slug", { length: 255 }).unique(), deletedAt: timestamp("deleted_at"), // Soft delete createdAt: timestamp("created_at").defaultNow(), });
- Use
for constrained values instead of varcharpgEnum() - Always include
/createdAt
timestampsupdatedAt - Add
for soft deletesdeletedAt - Set
on foreign keys to prevent orphaned recordsonDelete: "cascade" - Use
oruuid().defaultRandom()
for primary keysinteger().generatedAlwaysAsIdentity()
Full schema examples (enums, relations, junction tables, identity columns) in examples/core.md.
Pattern 3: Relational Queries with .with()
.with()Fetch related data efficiently in a single SQL query using
.with():
const job = await db.query.jobs.findFirst({ where: and(eq(jobs.id, jobId), isNull(jobs.deletedAt)), with: { company: { with: { locations: true } }, jobSkills: { with: { skill: true } }, }, }); // Result is fully typed: job.company.name, job.jobSkills[0].skill.name
- Use
withdb.query
when fetching related data -- single SQL query, no N+1.with() - Use query builder (
) for custom column selection, complex JOINs, aggregationsdb.select() - Always include
in WHERE conditions for soft-deleted tablesisNull(deletedAt)
Full relational query examples, N+1 anti-patterns, and dynamic filtering in examples/queries.md.
</patterns>Additional Patterns
The following patterns are documented with full examples in examples/:
- Query Builder - Complex filters, dynamic conditions, custom JOINs - see queries.md
- Transactions - Atomic operations, error handling, rollback - see transactions.md
- Database Migrations - Drizzle Kit workflow,
vsgenerate
- see migrations.mdpush - Database Seeding - Development data, safe cleanup - see seeding.md
Performance optimization (indexes, prepared statements, pagination) is documented in reference.md.
<red_flags>
RED FLAGS
- ❌ Using
instead ofdb
inside transactions - Bypasses transaction context, breaking atomicitytx - ❌ N+1 queries with relations - Use
to fetch in one query.with() - ❌ Not setting
- Field name mismatches between JS and SQLcasing: 'snake_case' - ❌ Using v1
per-table syntax - Deprecated, userelations()defineRelations() - ❌ Using callback-based
/where
- v1 syntax deprecated, use object-based syntaxorderBy - ⚠️ Queries without soft delete checks (
)isNull(deletedAt) - ⚠️ No pagination limits on list queries
Gotchas & Edge Cases:
- Neon HTTP has 30-second query timeout - long queries need WebSocket
- Prepared statements created outside transactions cannot be used inside transactions
deprecated in v1.0.0-beta.1 - useenableRLS()
insteadpgTable.withRLS()- Validator packages consolidated:
is nowdrizzle-zod
(since v1 beta)drizzle-orm/zod
For the complete list of anti-patterns and gotchas, see reference.md.
</red_flags>
<critical_reminders>
CRITICAL REMINDERS
All code must follow project conventions in CLAUDE.md
(You MUST set
in Drizzle config to map camelCase JS to snake_case SQL)casing: 'snake_case'
(You MUST use
parameter (NOT tx
) inside transaction callbacks to ensure atomicity)db
(You MUST use
for relational queries to avoid N+1 problems - fetches all data in single SQL query).with()
(You MUST use
for RQB v2 - the old defineRelations()
per-table syntax is deprecated)relations()
Failure to follow these rules will cause field name mismatches, break transaction atomicity, create N+1 performance issues, and use deprecated APIs.
</critical_reminders>