From harness-claude
Optimizes Drizzle ORM queries with prepared statements, db.batch(), EXPLAIN analysis, joins for N+1 avoidance, indexing, pagination, and bulk operations for better performance.
npx claudepluginhub intense-visions/harness-engineering --plugin harness-claudeThis skill uses the workspace's default tool permissions.
> Optimize Drizzle queries with prepared statements, db.batch(), explain analysis, and join-based N+1 avoidance
Guides Drizzle ORM type-safe schema design, relational queries, prepared statements, migrations, and transactions. Use for database schema, queries, migrations, and performance optimization in TypeScript.
Provides expertise in Drizzle ORM for TypeScript: schema design, relational queries, Drizzle Kit migrations, and serverless integrations with Neon, Supabase, PlanetScale.
Guides PostgreSQL and Drizzle ORM usage for type-safe schemas, relations, migrations, queries, indexes, and performance in backend APIs and data models.
Share bugs, ideas, or general feedback.
Optimize Drizzle queries with prepared statements, db.batch(), explain analysis, and join-based N+1 avoidance
const getUserById = db
.select()
.from(users)
.where(eq(users.id, sql.placeholder('id')))
.prepare('get_user_by_id');
// Reuse — skips query parsing on subsequent calls
const user = await getUserById.execute({ id: userId });
// Bad: fetches all columns
const all = await db.select().from(users);
// Good: fetches only what the UI needs
const summary = await db
.select({
id: users.id,
name: users.name,
})
.from(users);
// N+1 anti-pattern
const allUsers = await db.select().from(users);
for (const user of allUsers) {
const userPosts = await db.select().from(posts).where(eq(posts.authorId, user.id));
}
// Fixed: single query with join
const usersWithPosts = await db
.select({
user: users,
post: posts,
})
.from(users)
.leftJoin(posts, eq(users.id, posts.authorId));
const usersWithPosts = await db.query.users.findMany({
with: { posts: { limit: 10 } },
});
db.batch() (supported by Neon, LibSQL):const [users, posts, stats] = await db.batch([
db.select().from(users).limit(10),
db.select().from(posts).where(eq(posts.published, true)).limit(10),
db.select({ count: sql<number>`count(*)` }).from(users),
]);
export const posts = pgTable(
'posts',
{
authorId: uuid('author_id').notNull(),
createdAt: timestamp('created_at').notNull().defaultNow(),
status: text('status').notNull(),
},
(table) => [
index('posts_author_idx').on(table.authorId),
index('posts_status_created_idx').on(table.status, table.createdAt),
]
);
const page = await db
.select()
.from(posts)
.where(gt(posts.id, lastSeenId))
.orderBy(asc(posts.id))
.limit(20);
const plan = await db.execute(
sql`EXPLAIN ANALYZE SELECT * FROM posts WHERE author_id = ${authorId}`
);
console.log(plan.rows);
Look for sequential scans on large tables — add indexes for filtered columns.
await db.insert(events).values(items.map((item) => ({ type: item.type, payload: item.data })));
import { Pool } from '@neondatabase/serverless';
import { drizzle } from 'drizzle-orm/neon-serverless';
const pool = new Pool({ connectionString: process.env.DATABASE_URL });
const db = drizzle(pool, { schema });
Drizzle generates SQL that closely mirrors what you write. Unlike heavier ORMs, there is minimal abstraction overhead — the generated SQL is predictable and easy to optimize.
Prepared statements: The first execution compiles and caches the query plan. Subsequent executions reuse it. This reduces parsing overhead by 10-30% for complex queries. Name prepared statements uniquely — duplicate names cause errors.
db.batch() support: Currently available for Neon HTTP, LibSQL/Turso, and D1. It sends multiple queries in a single HTTP request, reducing round-trip latency. Not available for standard PostgreSQL drivers (use transactions instead for batching).
Relational API query generation: The with clause generates subqueries, not JOINs. For findMany({ with: { posts: true } }), Drizzle executes two queries: one for users and one for posts with WHERE author_id IN (...). This avoids row multiplication from JOINs on one-to-many relations.
Query complexity vs performance:
Trade-offs:
db.batch() is driver-specific — code that uses it is not portable across drivershttps://orm.drizzle.team/docs/performance