Prisma dominated the TypeScript ORM conversation for a few years, and for good reason. Type safety, auto-generated client, schema migrations out of the box. But as projects grew and the caveats stacked up — the binary dependency, the N+1 quirks, the runtime that doesn’t behave quite like SQL — a growing number of teams started looking for alternatives.
Drizzle ORM is the one that’s stuck. It’s been in active development since 2022 and by 2026 it’s the most commonly reached-for ORM in new TypeScript projects, particularly those using Bun, Cloudflare Workers, or edge runtimes where Prisma’s binary client is a problem. Here’s why it’s worth your attention, and how to actually use it.
The Core Idea
Drizzle describes itself as a “SQL-like” ORM. The distinction matters. Most ORMs abstract SQL into object method chains that can produce surprising query plans if you’re not careful. Drizzle’s query syntax is intentionally close to SQL, so the query you write maps fairly directly to the query that runs.
You define your schema in TypeScript, and the schema definition is both the source of truth for your database structure and the type information for your queries. No separate schema file in a domain-specific language, no code generation step that runs separately from your types — the schema is TypeScript.
A table definition:
import { pgTable, serial, text, timestamp, boolean } from 'drizzle-orm/pg-core';
export const users = pgTable('users', {
id: serial('id').primaryKey(),
email: text('email').notNull().unique(),
name: text('name').notNull(),
createdAt: timestamp('created_at').defaultNow().notNull(),
active: boolean('active').default(true).notNull(),
});
export const posts = pgTable('posts', {
id: serial('id').primaryKey(),
title: text('title').notNull(),
content: text('content'),
authorId: integer('author_id').references(() => users.id),
publishedAt: timestamp('published_at'),
});
This is just TypeScript. No decorators, no separate DSL, no magic. The pgTable function knows you’re targeting PostgreSQL; there are equivalents for MySQL, SQLite, and other drivers.
Querying
Drizzle has two query interfaces: the relational API (higher-level, handles joins automatically) and the SQL-like query builder (lower-level, gives you full control). Both are fully typed.
The relational API is great for common CRUD:
import { drizzle } from 'drizzle-orm/node-postgres';
import { eq, and, gte } from 'drizzle-orm';
import { Pool } from 'pg';
const pool = new Pool({ connectionString: process.env.DATABASE_URL });
const db = drizzle(pool, { schema: { users, posts } });
// Select user by email with posts
const userWithPosts = await db.query.users.findFirst({
where: eq(users.email, 'alice@example.com'),
with: {
posts: {
where: and(
eq(posts.authorId, users.id),
gte(posts.publishedAt, new Date('2026-01-01'))
),
orderBy: [desc(posts.publishedAt)],
limit: 10,
},
},
});
// Type: { id: number; email: string; name: string; posts: Post[] } | undefined
The with clause tells Drizzle to join the related table — it generates a single efficient query with a join rather than multiple round-trips. The return type is inferred: TypeScript knows exactly what shape the result has, including the nested posts array.
For complex queries where you need raw SQL control:
// Complex aggregation with grouping
const stats = await db
.select({
authorId: posts.authorId,
postCount: sql<number>`count(*)`,
latestPost: sql<Date>`max(${posts.publishedAt})`,
})
.from(posts)
.where(isNotNull(posts.publishedAt))
.groupBy(posts.authorId)
.having(sql`count(*) > 5`);
The sql template tag lets you drop into raw SQL when you need to, while keeping the result typed.
Migrations with Drizzle Kit
Drizzle Kit is the companion CLI for schema migrations. The workflow:
- You update your schema TypeScript file
- Run
npx drizzle-kit generate— Drizzle compares your schema to what’s currently in the database and generates a SQL migration file - Review the generated SQL (it’s just SQL, readable and editable)
- Run
npx drizzle-kit migrateto apply it
# After updating your schema.ts
npx drizzle-kit generate
# Generated: drizzle/0003_add_posts_table.sql
# Contents (Drizzle generates this, you review it):
# CREATE TABLE posts (
# id SERIAL PRIMARY KEY,
# title TEXT NOT NULL,
# ...
# );
# Apply
npx drizzle-kit migrate
The generated SQL files are committed to your repository alongside your code. This gives you an auditable migration history in plain SQL that any DBA can read, review, or manually adjust if needed. No binary state files, no migration framework lock-in — just SQL files in version control.
For local development, npx drizzle-kit push skips the migration file and applies changes directly to a local database, which is faster for iteration.
Why It Works Well with Bun and Edge Runtimes
Prisma’s binary client doesn’t run in Cloudflare Workers, Bun, or Deno by default. This has been a real problem for teams building on these runtimes. Drizzle is just TypeScript — there’s no binary. If your runtime can execute JavaScript, Drizzle runs.
With Bun and a local SQLite database:
import { drizzle } from 'drizzle-orm/bun-sqlite';
import { Database } from 'bun:sqlite';
const sqlite = new Database('local.db');
const db = drizzle(sqlite, { schema });
// Same query API as PostgreSQL -- Drizzle handles the driver difference
const allUsers = await db.select().from(users);
Switch from SQLite to PostgreSQL for production by changing the import and connection string. The schema and query code doesn’t change. This makes local development with SQLite + production PostgreSQL a realistic pattern rather than a maintenance headache.
Drizzle vs Prisma in 2026
The honest comparison: Prisma still has advantages in its developer experience for people new to it (the generated client is very ergonomic) and in its ecosystem (more third-party integrations). Prisma 6’s performance improvements have closed some of the gap on the N+1 and raw performance issues.
Drizzle’s advantages are: zero runtime overhead (the ORM is compiled away, no runtime binary), SQL-native query model that maps predictably to actual database queries, first-class Bun and edge runtime support, and the straightforward migration workflow with plain SQL files.
For teams starting new TypeScript projects in 2026, particularly on Bun or edge runtimes, Drizzle is the natural default. For teams already on Prisma with existing schemas and tooling, the migration cost needs to justify the switch — and for most projects it probably doesn’t unless you’re hitting specific pain points.
Setting up Drizzle on a new Node.js or Bun project takes about 15 minutes. The documentation at orm.drizzle.team is excellent. If you’ve been on Prisma for years and haven’t looked at alternatives, it’s worth spending an afternoon on a side project with Drizzle just to understand what the comparison actually looks like in practice.