Drizzle ORM — The Lightweight TypeScript ORM Reshaping How We Write SQL
Posted on: 4/27/2026 7:15:16 AM
Table of contents
- 1. What Is Drizzle ORM?
- 2. Architecture: Why Is Drizzle So Small?
- 3. Schema Definition — Code-First in TypeScript
- 4. Query Builder — The SQL You Already Know
- 5. Relational Queries — When You Want API-Style Access
- 6. Performance Comparison: Drizzle vs Prisma 7
- 7. Migrations — Two Flexible Modes
- 8. Database Support
- 9. Integration with Vue.js and Nuxt
- 10. When to Choose Drizzle vs Prisma
- 11. Quick Start — 5 Minutes with Drizzle
- 12. Conclusion
1. What Is Drizzle ORM?
Drizzle ORM is a SQL-first TypeScript ORM — meaning you write schemas and queries that closely mirror raw SQL, wrapped in TypeScript with full type safety. There's no separate query engine, no Rust binary, no code generation step — everything is pure TypeScript from start to finish.
While Prisma abstracts SQL behind a custom DSL (.prisma schema files), Drizzle takes the opposite approach: keeping the SQL mental model intact while adding type safety and a modern developer experience. The philosophy: "If you know SQL, you already know Drizzle."
Why Drizzle Rose to Prominence in 2026
The explosion of edge computing (Cloudflare Workers, Vercel Edge Functions) created strict requirements around bundle size and cold start times. Prisma 5.x with its ~13MB Rust query engine was nearly impossible to deploy on the edge. Drizzle with its ~12KB runtime became the natural choice for edge-first applications.
2. Architecture: Why Is Drizzle So Small?
graph TB
subgraph Prisma["Prisma Architecture"]
PA["Schema DSL
(.prisma file)"] -->|"prisma generate"| PB["Generated Client
+ Types"]
PB --> PC["TypeScript/WASM
Query Engine ~1.6MB"]
PC --> PD["Database Driver"]
end
subgraph Drizzle["Drizzle Architecture"]
DA["TypeScript Schema
(tables, columns)"] -->|"Direct import"| DB["Query Builder
~12KB total"]
DB --> DC["Database Driver"]
end
PD --> E["PostgreSQL / MySQL
SQLite / SQL Server"]
DC --> E
style PA fill:#f8f9fa,stroke:#e0e0e0,color:#2c3e50
style PB fill:#f8f9fa,stroke:#e0e0e0,color:#2c3e50
style PC fill:#ff9800,stroke:#fff,color:#fff
style PD fill:#f8f9fa,stroke:#e0e0e0,color:#2c3e50
style DA fill:#f8f9fa,stroke:#e94560,color:#2c3e50
style DB fill:#e94560,stroke:#fff,color:#fff
style DC fill:#f8f9fa,stroke:#e94560,color:#2c3e50
style E fill:#2c3e50,stroke:#fff,color:#fff
Drizzle eliminates the middleware layer entirely. Schemas are defined using pure TypeScript — pgTable(), mysqlTable(), sqliteTable() — and the query builder reads this schema to produce SQL strings directly. No generate step, no intermediate files, types always stay in sync with source code.
3. Schema Definition — Code-First in TypeScript
One of Drizzle's strongest points: the schema is real TypeScript code, not a custom DSL.
// schema.ts — Drizzle schema definition
import { pgTable, serial, text, integer, 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(),
avatarUrl: text('avatar_url'),
isActive: boolean('is_active').default(true),
createdAt: timestamp('created_at').defaultNow(),
});
export const posts = pgTable('posts', {
id: serial('id').primaryKey(),
title: text('title').notNull(),
content: text('content'),
authorId: integer('author_id')
.notNull()
.references(() => users.id),
publishedAt: timestamp('published_at'),
viewCount: integer('view_count').default(0),
});
// Types are inferred automatically — no generation needed
type User = typeof users.$inferSelect;
type NewUser = typeof users.$inferInsert;
Compare with the equivalent Prisma schema:
// schema.prisma — Prisma DSL
model User {
id Int @id @default(autoincrement())
email String @unique
name String
avatarUrl String? @map("avatar_url")
isActive Boolean @default(true) @map("is_active")
createdAt DateTime @default(now()) @map("created_at")
posts Post[]
@@map("users")
}
model Post {
id Int @id @default(autoincrement())
title String
content String?
authorId Int @map("author_id")
author User @relation(fields: [authorId], references: [id])
publishedAt DateTime? @map("published_at")
viewCount Int @default(0) @map("view_count")
@@map("posts")
}
// Then you must run: npx prisma generate
The Code-First Advantage
Because the schema is TypeScript, you can leverage every language feature: split schemas across multiple files, import/export, conditional logic, shared utilities. You're not limited by a custom DSL's syntax.
4. Query Builder — The SQL You Already Know
Drizzle's query builder maps nearly 1:1 to SQL syntax. If you can write SQL, you can write Drizzle.
import { db } from './db';
import { users, posts } from './schema';
import { eq, gt, desc, sql, and, like } from 'drizzle-orm';
// Basic SELECT
const allUsers = await db.select().from(users);
// WHERE + ORDER BY
const activeUsers = await db
.select()
.from(users)
.where(eq(users.isActive, true))
.orderBy(desc(users.createdAt));
// JOIN
const usersWithPosts = await db
.select({
userName: users.name,
postTitle: posts.title,
views: posts.viewCount,
})
.from(users)
.innerJoin(posts, eq(users.id, posts.authorId))
.where(gt(posts.viewCount, 100));
// INSERT returning
const [newUser] = await db
.insert(users)
.values({ email: 'dev@example.com', name: 'Dev' })
.returning();
// UPDATE
await db
.update(posts)
.set({ viewCount: sql`${posts.viewCount} + 1` })
.where(eq(posts.id, 42));
// Subquery with aggregation
const topAuthors = await db
.select({
name: users.name,
totalViews: sql<number>`sum(${posts.viewCount})`.as('total_views'),
})
.from(users)
.innerJoin(posts, eq(users.id, posts.authorId))
.groupBy(users.name)
.having(gt(sql`sum(${posts.viewCount})`, 1000))
.orderBy(desc(sql`total_views`));
5. Relational Queries — When You Want API-Style Access
Alongside the SQL-style query builder, Drizzle also provides a Relational Query API for those who prefer Prisma-style nested queries:
import { relations } from 'drizzle-orm';
// Define relations
export const usersRelations = relations(users, ({ many }) => ({
posts: many(posts),
}));
export const postsRelations = relations(posts, ({ one }) => ({
author: one(users, {
fields: [posts.authorId],
references: [users.id],
}),
}));
// Query with nested relations — Prisma-style
const result = await db.query.users.findMany({
with: {
posts: {
where: gt(posts.viewCount, 50),
orderBy: desc(posts.publishedAt),
limit: 5,
},
},
where: eq(users.isActive, true),
});
6. Performance Comparison: Drizzle vs Prisma 7
| Metric | Drizzle 0.45.x | Prisma 7.x | Prisma 5.x (legacy) |
|---|---|---|---|
| Bundle size | ~12KB | ~1.6MB (600KB gzip) | ~13MB |
| Cold start (Lambda) | 50-100ms | 80-150ms | 600-1800ms |
| Simple query overhead | 0.5-1ms | 1-2ms | 3-5ms |
| Complex join overhead | 1-3ms | 2-5ms | 5-10ms |
| Code generation | Not required | Requires prisma generate | Required |
| Dependencies | 0 | Multiple | Multiple + Rust binary |
| Tree-shakeable | Yes | Partial | No |
Benchmarks in Real-World Context
The query overhead difference (0.5ms vs 2ms) is rarely the bottleneck. Database round-trip latency is typically 5-50ms — ORM overhead accounts for only 2-10% of total query time. Drizzle truly excels in bundle size and cold start — two critical factors for edge and serverless deployments.
7. Migrations — Two Flexible Modes
Drizzle Kit offers two migration workflows:
7.1 Generate + Migrate (Production)
# Generate SQL migration file from schema changes
npx drizzle-kit generate --name add_user_avatar
# Review the SQL to be executed
cat drizzle/0001_add_user_avatar.sql
# Apply the migration
npx drizzle-kit migrate
7.2 Push (Prototyping)
# Push schema directly to DB, no migration file created
npx drizzle-kit push
# Suitable for rapid development, NOT for production
7.3 Drizzle Studio — Data Management GUI
# Open web UI to browse and edit data
npx drizzle-kit studio
Drizzle Studio runs in the browser, supports schema viewing, data querying, and direct record editing — similar to Prisma Studio but without separate installation.
8. Database Support
| Database | Driver | Edge-compatible |
|---|---|---|
| PostgreSQL | postgres, pg, @neondatabase/serverless | Neon, Supabase |
| MySQL | mysql2, @planetscale/database | PlanetScale |
| SQLite | better-sqlite3, @libsql/client | Turso, Cloudflare D1 |
| SQL Server | Community driver | Limited |
| Gel (new) | Dedicated Gel dialect | In development |
Optimal Edge Database Stack
Combine Drizzle + Turso (libSQL on edge) or Drizzle + Cloudflare D1 for a fully edge-native database layer — zero latency to database, near-instant cold starts. This is the stack many startups are choosing in 2026.
9. Integration with Vue.js and Nuxt
Drizzle works perfectly in Nuxt server routes and Vue full-stack apps:
// server/utils/db.ts — Nuxt 4 server utility
import { drizzle } from 'drizzle-orm/postgres-js';
import postgres from 'postgres';
import * as schema from '~/server/db/schema';
const client = postgres(process.env.DATABASE_URL!);
export const db = drizzle(client, { schema });
// server/api/posts.get.ts — API route
export default defineEventHandler(async () => {
return db.query.posts.findMany({
with: { author: true },
where: eq(schema.posts.publishedAt, sql`IS NOT NULL`),
orderBy: desc(schema.posts.publishedAt),
limit: 20,
});
});
10. When to Choose Drizzle vs Prisma
graph TD
A["Choose a TypeScript ORM"] --> B{"Deploying to edge?
Bundle size matters?"}
B -->|"Yes"| C["✅ Drizzle"]
B -->|"No"| D{"Team comfortable
with SQL?"}
D -->|"Yes"| E["✅ Drizzle"]
D -->|"No, want higher
abstraction"| F["✅ Prisma"]
A --> G{"Need rich ecosystem
(Studio, Accelerate)?"}
G -->|"Yes"| H["✅ Prisma"]
G -->|"No, need lightweight
and fast"| I["✅ Drizzle"]
style A fill:#2c3e50,stroke:#fff,color:#fff
style C fill:#e94560,stroke:#fff,color:#fff
style E fill:#e94560,stroke:#fff,color:#fff
style F fill:#4CAF50,stroke:#fff,color:#fff
style H fill:#4CAF50,stroke:#fff,color:#fff
style I fill:#e94560,stroke:#fff,color:#fff
style B fill:#f8f9fa,stroke:#e0e0e0,color:#2c3e50
style D fill:#f8f9fa,stroke:#e0e0e0,color:#2c3e50
style G fill:#f8f9fa,stroke:#e0e0e0,color:#2c3e50
| ✅ Choose Drizzle | ✅ Choose Prisma |
|---|---|
| Edge / serverless deploy (small bundle) | Team new to SQL, needs abstraction |
| Team proficient in SQL | Large project needing rich ecosystem |
| No code generation step desired | Prefer clear schema DSL |
| Maximum tree-shaking needed | Need Prisma Accelerate (connection pooling) |
| Multiple database backends | Team already familiar with Prisma workflow |
11. Quick Start — 5 Minutes with Drizzle
# Installation
npm install drizzle-orm postgres
npm install -D drizzle-kit
# Create config
# 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!,
},
});
# Create schema, push to DB, start coding
npx drizzle-kit push
npx drizzle-kit studio # Open GUI
12. Conclusion
Drizzle ORM represents a new generation of tooling: lightweight, type-safe, SQL-first, and optimized for the edge computing era. With a bundle size of just 12KB, zero dependencies, and cold starts twice as fast as Prisma, Drizzle is the natural choice for projects deploying to Cloudflare Workers, Vercel Edge, or any serverless environment.
Prisma remains a powerful ORM with a robust ecosystem — especially after Prisma 7 removed the Rust engine. But if you want an ORM that "thinks with you" rather than "thinks for you," Drizzle deserves a try.
Where to Start?
If using Nuxt/Vue: check out the Drizzle Quick Start + set up server/utils/db.ts. For plain Node.js: npm install drizzle-orm postgres and start defining your schema. The entire process from install to first query takes about 5 minutes.
References:
ClickHouse 26.x — Columnar Database for Billion-Row Real-Time Analytics
Vite 8 + Rolldown: The Unified Rust Bundler That Changes JavaScript Builds
Disclaimer: The opinions expressed in this blog are solely my own and do not reflect the views or opinions of my employer or any affiliated organizations. The content provided is for informational and educational purposes only and should not be taken as professional advice. While I strive to provide accurate and up-to-date information, I make no warranties or guarantees about the completeness, reliability, or accuracy of the content. Readers are encouraged to verify the information and seek independent advice as needed. I disclaim any liability for decisions or actions taken based on the content of this blog.