Drizzle ORM Guide — TypeScript ORM with SQL-First Design

Learn Drizzle ORM: type-safe SQL queries in TypeScript, schema definition, migrations, and integration with PostgreSQL, MySQL, and SQLite.

What Is Drizzle ORM?

Drizzle is a TypeScript ORM with a SQL-first philosophy — rather than abstracting away SQL, it gives you type-safe query builders that map directly to SQL syntax. The result is code that feels familiar to SQL developers while catching type errors at compile time.

Drizzle is significantly lighter than Prisma (no Rust engine, no codegen daemon) and supports Postgres, MySQL, SQLite, and D1 (Cloudflare).

Schema Definition

// schema.ts
import { pgTable, serial, text, timestamp, boolean, integer } from 'drizzle-orm/pg-core'
import { relations } from 'drizzle-orm'

export const users = pgTable('users', {
  id: serial('id').primaryKey(),
  name: text('name').notNull(),
  email: text('email').notNull().unique(),
  createdAt: timestamp('created_at').defaultNow().notNull(),
})

export const posts = pgTable('posts', {
  id: serial('id').primaryKey(),
  title: text('title').notNull(),
  body: text('body'),
  published: boolean('published').default(false).notNull(),
  userId: integer('user_id').references(() => users.id),
})

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

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

Querying Data

import { drizzle } from 'drizzle-orm/postgres-js'
import postgres from 'postgres'
import { eq, and, desc, like } from 'drizzle-orm'
import { users, posts } from './schema'

const client = postgres(process.env.DATABASE_URL!)
const db = drizzle(client, { schema: { users, posts } })

// SELECT with JOIN using relations
const result = await db.query.users.findMany({
  with: {
    posts: {
      where: eq(posts.published, true),
      orderBy: desc(posts.createdAt),
      limit: 5,
    },
  },
})

// Raw query builder
const publishedPosts = await db
  .select({ id: posts.id, title: posts.title, author: users.name })
  .from(posts)
  .leftJoin(users, eq(posts.userId, users.id))
  .where(and(eq(posts.published, true), like(posts.title, '%typescript%')))
  .orderBy(desc(posts.createdAt))
  .limit(20)

Migrations

# Generate migration from schema changes
npx drizzle-kit generate

# Apply migrations
npx drizzle-kit migrate

# Push schema directly (development only)
npx drizzle-kit push
// drizzle.config.ts
import { defineConfig } from 'drizzle-kit'

export default defineConfig({
  schema: './src/schema.ts',
  out: './drizzle',
  dialect: 'postgresql',
  dbCredentials: {
    url: process.env.DATABASE_URL!,
  },
})

Drizzle vs Prisma

  • Bundle size: Drizzle is ~100KB, Prisma requires a Rust-based query engine (~40MB). Drizzle is the only choice for edge runtimes (Cloudflare Workers, Vercel Edge).
  • Query style: Drizzle is SQL-first; Prisma uses a higher-level abstraction. Drizzle queries are easier to optimize because you control the SQL.
  • Migrations: Both support auto-generated migrations. Drizzle's are plain SQL files; Prisma's are proprietary format.
  • Ecosystem: Prisma has a larger community and more documentation. Drizzle is growing fast, especially in the Next.js/Vercel ecosystem.

Frequently Asked Questions

Does Drizzle support transactions?

Yes. Use db.transaction(async (tx) => {{ ... }}). The transaction object has the same query API as db but all operations are wrapped in a database transaction with automatic rollback on error.

Can I use Drizzle with Cloudflare D1?

Yes. Drizzle has a drizzle-orm/d1 adapter that works with Cloudflare D1 (SQLite at the edge). Initialize with drizzle(env.DB) where env.DB is your D1 binding.

Is there a Drizzle Studio for browsing data?

Yes. npx drizzle-kit studio starts a local web UI for browsing and editing your database, similar to Prisma Studio.