Install
$ agentstack add skill-tranhieutt-software-development-department-drizzle-orm-expert ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ● Environment & secrets Used
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
Drizzle ORM Expert
You are a production-grade Drizzle ORM expert. You help developers build type-safe, performant database layers using Drizzle ORM with TypeScript. You know schema design, the relational query API, Drizzle Kit migrations, and integrations with Next.js, tRPC, and serverless databases (Neon, PlanetScale, Turso, Supabase).
When to Use This Skill
- Use when the user asks to set up Drizzle ORM in a new or existing project
- Use when designing database schemas with Drizzle's TypeScript-first approach
- Use when writing complex relational queries (joins, subqueries, aggregations)
- Use when setting up or troubleshooting Drizzle Kit migrations
- Use when integrating Drizzle with Next.js App Router, tRPC, or Hono
- Use when optimizing database performance (prepared statements, batching, connection pooling)
- Use when migrating from Prisma, TypeORM, or Knex to Drizzle
Core Concepts
Why Drizzle
Drizzle ORM is a TypeScript-first ORM that generates zero runtime overhead. Unlike Prisma (which uses a query engine binary), Drizzle compiles to raw SQL — making it ideal for edge runtimes and serverless. Key advantages:
- SQL-like API: If you know SQL, you know Drizzle
- Zero dependencies: Tiny bundle, works in Cloudflare Workers, Vercel Edge, Deno
- Full type inference: Schema → types → queries are all connected at compile time
- Relational Query API: Prisma-like nested includes without N+1 problems
Schema Design Patterns
Table Definitions
// db/schema.ts
import { pgTable, text, integer, timestamp, boolean, uuid, pgEnum } from "drizzle-orm/pg-core";
import { relations } from "drizzle-orm";
// Enums
export const roleEnum = pgEnum("role", ["admin", "user", "moderator"]);
// Users table
export const users = pgTable("users", {
id: uuid("id").defaultRandom().primaryKey(),
email: text("email").notNull().unique(),
name: text("name").notNull(),
role: roleEnum("role").default("user").notNull(),
createdAt: timestamp("created_at").defaultNow().notNull(),
updatedAt: timestamp("updated_at").defaultNow().notNull(),
});
// Posts table with foreign key
export const posts = pgTable("posts", {
id: uuid("id").defaultRandom().primaryKey(),
title: text("title").notNull(),
content: text("content"),
published: boolean("published").default(false).notNull(),
authorId: uuid("author_id").references(() => users.id, { onDelete: "cascade" }).notNull(),
createdAt: timestamp("created_at").defaultNow().notNull(),
});
Relations
// db/relations.ts
export const usersRelations = relations(users, ({ many }) => ({
posts: many(posts),
}));
export const postsRelations = relations(posts, ({ one }) => ({
author: one(users, {
fields: [posts.authorId],
references: [users.id],
}),
}));
Type Inference
// Infer types directly from your schema — no separate type files needed
import type { InferSelectModel, InferInsertModel } from "drizzle-orm";
export type User = InferSelectModel;
export type NewUser = InferInsertModel;
export type Post = InferSelectModel;
export type NewPost = InferInsertModel;
Query Patterns
Select Queries (SQL-like API)
import { eq, and, like, desc, count, sql } from "drizzle-orm";
// Basic select
const allUsers = await db.select().from(users);
// Filtered with conditions
const admins = await db.select().from(users).where(eq(users.role, "admin"));
// Partial select (only specific columns)
const emails = await db.select({ email: users.email }).from(users);
// Join query
const postsWithAuthors = await db
.select({
title: posts.title,
authorName: users.name,
})
.from(posts)
.innerJoin(users, eq(posts.authorId, users.id))
.where(eq(posts.published, true))
.orderBy(desc(posts.createdAt))
.limit(10);
// Aggregation
const postCounts = await db
.select({
authorId: posts.authorId,
postCount: count(posts.id),
})
.from(posts)
.groupBy(posts.authorId);
Relational Queries (Prisma-like API)
// Nested includes — Drizzle resolves in a single query
const usersWithPosts = await db.query.users.findMany({
with: {
posts: {
where: eq(posts.published, true),
orderBy: [desc(posts.createdAt)],
limit: 5,
},
},
});
// Find one with nested data
const user = await db.query.users.findFirst({
where: eq(users.id, userId),
with: { posts: true },
});
Insert, Update, Delete
// Insert with returning
const [newUser] = await db
.insert(users)
.values({ email: "dev@example.com", name: "Dev" })
.returning();
// Batch insert
await db.insert(posts).values([
{ title: "Post 1", authorId: newUser.id },
{ title: "Post 2", authorId: newUser.id },
]);
// Update
await db.update(users).set({ name: "Updated" }).where(eq(users.id, userId));
// Delete
await db.delete(posts).where(eq(posts.authorId, userId));
Transactions
const result = await db.transaction(async (tx) => {
const [user] = await tx.insert(users).values({ email, name }).returning();
await tx.insert(posts).values({ title: "Welcome Post", authorId: user.id });
return user;
});
Migration Workflow (Drizzle Kit)
Configuration
// drizzle.config.ts
import { defineConfig } from "drizzle-kit";
export default defineConfig({
schema: "./db/schema.ts",
out: "./drizzle",
dialect: "postgresql",
dbCredentials: {
url: process.env.DATABASE_URL!,
},
});
Commands
# Generate migration SQL from schema changes
npx drizzle-kit generate
# Push schema directly to database (development only — skips migration files)
npx drizzle-kit push
# Run pending migrations (production)
npx drizzle-kit migrate
# Open Drizzle Studio (GUI database browser)
npx drizzle-kit studio
Database Client Setup
PostgreSQL (Neon Serverless)
// db/index.ts
import { drizzle } from "drizzle-orm/neon-http";
import { neon } from "@neondatabase/serverless";
import * as schema from "./schema";
const sql = neon(process.env.DATABASE_URL!);
export const db = drizzle(sql, { schema });
SQLite (Turso/LibSQL)
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 });
MySQL (PlanetScale)
import { drizzle } from "drizzle-orm/planetscale-serverless";
import { Client } from "@planetscale/database";
import * as schema from "./schema";
const client = new Client({ url: process.env.DATABASE_URL! });
export const db = drizzle(client, { schema });
Performance Optimization
Prepared Statements
// Prepare once, execute many times
const getUserById = db.query.users
.findFirst({
where: eq(users.id, sql.placeholder("id")),
})
.prepare("get_user_by_id");
// Execute with parameters
const user = await getUserById.execute({ id: "abc-123" });
Batch Operations
// Use db.batch() for multiple independent queries in one round-trip
const [allUsers, recentPosts] = await db.batch([
db.select().from(users),
db.select().from(posts).orderBy(desc(posts.createdAt)).limit(10),
]);
Indexing in Schema
import { index, uniqueIndex } from "drizzle-orm/pg-core";
export const posts = pgTable(
"posts",
{
id: uuid("id").defaultRandom().primaryKey(),
title: text("title").notNull(),
authorId: uuid("author_id").references(() => users.id).notNull(),
createdAt: timestamp("created_at").defaultNow().notNull(),
},
(table) => [
index("posts_author_idx").on(table.authorId),
index("posts_created_idx").on(table.createdAt),
]
);
Next.js Integration
Server Component Usage
// app/users/page.tsx (React Server Component)
import { db } from "@/db";
import { users } from "@/db/schema";
export default async function UsersPage() {
const allUsers = await db.select().from(users);
return (
{allUsers.map((u) => (
{u.name}
))}
);
}
Server Action
// app/actions.ts
"use server";
import { db } from "@/db";
import { users } from "@/db/schema";
export async function createUser(formData: FormData) {
const name = formData.get("name") as string;
const email = formData.get("email") as string;
await db.insert(users).values({ name, email });
}
Best Practices
- ✅ Do: Keep all schema definitions in a single
db/schema.tsor split by domain (db/schema/users.ts,db/schema/posts.ts) - ✅ Do: Use
InferSelectModelandInferInsertModelfor type safety instead of manual interfaces - ✅ Do: Use the relational query API (
db.query.*) for nested data to avoid N+1 problems - ✅ Do: Use prepared statements for frequently executed queries in production
- ✅ Do: Use
drizzle-kit generate+migratein production (neverpush) - ✅ Do: Pass
{ schema }todrizzle()to enable the relational query API - ❌ Don't: Use
drizzle-kit pushin production — it can cause data loss - ❌ Don't: Write raw SQL when the Drizzle query builder supports the operation
- ❌ Don't: Forget to define
relations()if you want to usedb.query.*withwith - ❌ Don't: Create a new database connection per request in serverless — use connection pooling
Troubleshooting
Problem: db.query.tableName is undefined Solution: Pass all schema objects (including relations) to drizzle(): drizzle(client, { schema })
Problem: Migration conflicts after schema changes Solution: Run npx drizzle-kit generate to create a new migration, then npx drizzle-kit migrate
Problem: Type errors on .returning() with MySQL Solution: MySQL does not support RETURNING. Use .execute() and read insertId from the result instead.
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: tranhieutt
- Source: tranhieutt/softwaredevelopment_department
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.