What is Drizzle ORM
Drizzle ORM is a lightweight, TypeScript-first ORM. Compared to Prisma, it's "closer to SQL" with strong type inference, small bundle size, and works in Edge Runtime (Cloudflare Workers, etc.).
Key differences from Prisma:
- Schema defined in TypeScript (not .prisma files)
- Type inference is automatic
- Full Edge Runtime support
- Smaller bundle size
- More SQL-proximate (less abstraction)
"Writing Drizzle queries is writing SQL" is the underlying philosophy.
Schema Definition
import {
pgTable, serial, text, varchar, integer,
timestamp, boolean, decimal, jsonb, index, uniqueIndex, pgEnum,
} from "drizzle-orm/pg-core";
import { relations } from "drizzle-orm";
export const roleEnum = pgEnum("role", ["admin", "user", "viewer"]);
export const users = pgTable("users", {
id: serial("id").primaryKey(),
name: varchar("name", { length: 255 }).notNull(),
email: varchar("email", { length: 255 }).notNull().unique(),
role: roleEnum("role").default("user").notNull(),
metadata: jsonb("metadata").$type<Record<string, unknown>>().default({}),
createdAt: timestamp("created_at", { withTimezone: true }).defaultNow().notNull(),
}, (table) => ({
emailIdx: uniqueIndex("email_idx").on(table.email),
}));
export const posts = pgTable("posts", {
id: serial("id").primaryKey(),
title: text("title").notNull(),
authorId: integer("author_id").notNull().references(() => users.id, { onDelete: "cascade" }),
createdAt: timestamp("created_at", { withTimezone: true }).defaultNow().notNull(),
});
export const usersRelations = relations(users, ({ many }) => ({ posts: many(posts) }));
export const postsRelations = relations(posts, ({ one }) => ({
author: one(users, { fields: [posts.authorId], references: [users.id] }),
}));
export type User = typeof users.$inferSelect;
export type NewUser = typeof users.$inferInsert;
Database Connection and Basic Queries
import { drizzle } from "drizzle-orm/node-postgres";
import { Pool } from "pg";
import * as schema from "./schema";
import { eq, and, desc, like, sql } from "drizzle-orm";
const pool = new Pool({ connectionString: process.env.DATABASE_URL, max: 10 });
export const db = drizzle(pool, { schema });
async function findUser(id: number) {
const result = await db.select().from(users).where(eq(users.id, id)).limit(1);
return result[0];
}
async function findPostsWithAuthor(limit = 10) {
return db.query.posts.findMany({
with: { author: true },
orderBy: desc(posts.createdAt),
limit,
});
}
async function createUser(data: NewUser): Promise<User> {
const [user] = await db.insert(users).values(data).returning();
return user;
}
Dynamic Query Building
interface UserFilter {
role?: "admin" | "user" | "viewer";
search?: string;
createdAfter?: Date;
}
async function findUsers(filter: UserFilter, page = 1, pageSize = 20) {
const conditions = [];
if (filter.role) conditions.push(eq(users.role, filter.role));
if (filter.search) conditions.push(like(users.name, `%${filter.search}%`));
const whereClause = conditions.length > 0 ? and(...conditions) : undefined;
const [data, countResult] = await Promise.all([
db.select().from(users).where(whereClause)
.orderBy(desc(users.createdAt)).limit(pageSize).offset((page - 1) * pageSize),
db.select({ count: sql<number>`count(*)::int` }).from(users).where(whereClause),
]);
return { data, total: countResult[0].count, page, pageSize };
}
Transactions
async function transferCredits(fromUserId: number, toUserId: number, amount: number) {
await db.transaction(async (tx) => {
const [sender] = await tx.select().from(users).where(eq(users.id, fromUserId)).for("update");
const currentCredits = (sender.metadata as any)?.credits ?? 0;
if (currentCredits < amount) throw new Error("Insufficient credits");
await tx.update(users)
.set({ metadata: sql`jsonb_set(metadata, '{credits}', to_jsonb(${currentCredits - amount}))` })
.where(eq(users.id, fromUserId));
await tx.update(users)
.set({ metadata: sql`jsonb_set(COALESCE(metadata, '{}'), '{credits}', to_jsonb(COALESCE((metadata->>'credits')::int, 0) + ${amount}))` })
.where(eq(users.id, toUserId));
});
}
Migration Management
# Generate migration
npx drizzle-kit generate
# Apply migration
npx drizzle-kit migrate
# Check diff
npx drizzle-kit check
# Drizzle Studio (GUI)
npx drizzle-kit studio
Edge Environment Usage
import { drizzle } from "drizzle-orm/d1";
export default {
async fetch(req: Request, env: { DB: D1Database }): Promise<Response> {
const db = drizzle(env.DB, { schema });
const allUsers = await db.select().from(schema.users).limit(10);
return Response.json(allUsers);
},
};
Drizzle lets you leverage SQL knowledge while gaining TypeScript type safety. If you've been scared of "SQL hidden inside ORM black boxes," Drizzle is a great fit.
This article is from the Claude Code Complete Guide (7 chapters) on note.com.
myouga (@myougatheaxo) - VTuber axolotl. Sharing practical AI development tips.
Top comments (0)