TL;DR: Drizzle ORM is a lightweight TypeScript database tool where you define your tables in regular TypeScript files, write queries that look almost like raw SQL, and run migrations to keep your database in sync. It is faster and smaller than Prisma, works on edge runtimes, and gives you more control over what is happening with your data.
The Real Scenario: AI Used Drizzle Instead of Prisma
Here is what happened. You opened Claude Code, Cursor, or Windsurf and typed something like: "Build me a Next.js app with user authentication and a PostgreSQL database." You expected to see a prisma/schema.prisma file — the thing every tutorial mentions. Instead, AI generated a file called schema.ts inside a db/ folder, full of TypeScript code that defines tables. The queries look different too. Instead of prisma.user.findMany(), you see db.select().from(users).where(eq(users.role, 'admin')).
That is Drizzle ORM. And AI picked it for a reason.
Drizzle has been gaining momentum fast. It crossed 1.5 million weekly npm downloads in early 2026, and AI coding tools have started favoring it for new projects — especially ones that deploy to edge environments like Vercel Edge Functions, Cloudflare Workers, or use serverless databases like Neon and Turso. It is lighter than Prisma, has zero external dependencies, requires no code generation step, and its query syntax maps almost directly to SQL.
If Prisma is like having an interpreter translate between you and the database, Drizzle is like learning a few key phrases yourself. You are closer to the actual conversation.
Quick Recap: What Does an ORM Do?
Before diving into Drizzle specifically, here is the 30-second version of what an ORM does. (For the full explanation, read What Is an ORM?)
An ORM — Object-Relational Mapper — is a tool that lets you interact with your database using your programming language instead of writing raw SQL. Without an ORM, you would write something like SELECT * FROM users WHERE role = 'admin'. With an ORM, you write that same query in TypeScript or JavaScript, and the ORM translates it to SQL behind the scenes.
Why does this matter for AI coders? Because when you ask AI to "add a database," it needs to pick an ORM. For years, the default was Prisma. Now, AI tools increasingly reach for Drizzle — and understanding why helps you work with whatever your AI generates.
Drizzle vs Prisma: The Big Comparison
This is the question everyone asks, so let us address it directly. Both are ORMs for TypeScript. Both connect to PostgreSQL, MySQL, and SQLite. But they have fundamentally different philosophies.
Schema definition
Prisma uses its own language — the .prisma schema file. It is not TypeScript, not JavaScript, not SQL. It is a Prisma-specific format that you have to learn.
Drizzle defines schemas in regular TypeScript files. Your table definitions are just TypeScript code that your editor already understands. No new language to learn.
Query style
Prisma abstracts SQL completely. You write prisma.user.findMany({ where: { role: 'ADMIN' } }). It looks nothing like SQL, which is great if you never want to think about SQL — but makes it harder to understand what the database is actually doing.
Drizzle writes queries that mirror SQL structure: db.select().from(users).where(eq(users.role, 'admin')). If you have ever glanced at a SQL query, Drizzle code will look familiar. This means skills you learn with Drizzle transfer directly to understanding raw SQL.
Code generation
Prisma requires a prisma generate step every time you change your schema. This generates a typed client. Forget this step and your code breaks with confusing errors.
Drizzle has no code generation step. Your schema is TypeScript, your queries are TypeScript, and TypeScript already knows the types. One less thing that can go wrong.
Size and speed
Prisma installs a binary engine (several megabytes) and has a heavier runtime footprint. This makes it slower to start — especially in serverless environments where cold starts matter.
Drizzle is roughly 50KB with zero dependencies. It starts nearly instantly. For serverless and edge deployments, this difference is significant.
Edge runtime support
Prisma added edge support later and requires specific adapters. It works, but it was not designed for it.
Drizzle was built with edge runtimes in mind from day one. It works natively on Cloudflare Workers, Vercel Edge Functions, Deno, and Bun without adapters or workarounds.
When does each one make sense?
Pick Prisma when: You want maximum abstraction, you are working on a large team project with an existing Prisma setup, or you rely heavily on Prisma Studio for visual database browsing.
Pick Drizzle when: You want something lightweight, you are deploying to edge or serverless environments, you want your queries to look like SQL, or you are starting a new project and want fewer moving parts.
Neither choice is wrong. Prisma has a larger ecosystem and more documentation. Drizzle is leaner and faster. AI tools pick based on the project context — and increasingly, that context favors Drizzle. If your AI generates Drizzle code, that is not a mistake. It is a deliberate choice.
Key Drizzle Concepts
Drizzle has three core pieces you need to understand: the schema, migrations, and queries. Let us walk through each one.
1. Schema: Your Tables in TypeScript
In Drizzle, your database tables are defined as regular TypeScript code. Here is what a basic schema looks like:
// db/schema.ts
import { pgTable, serial, text, varchar, boolean, timestamp, integer } from 'drizzle-orm/pg-core';
export const users = pgTable('users', {
id: serial('id').primaryKey(),
name: text('name').notNull(),
email: varchar('email', { length: 255 }).notNull().unique(),
role: text('role').default('user'),
createdAt: timestamp('created_at').defaultNow(),
});
export const posts = pgTable('posts', {
id: serial('id').primaryKey(),
title: text('title').notNull(),
content: text('content'),
published: boolean('published').default(false),
authorId: integer('author_id').references(() => users.id),
createdAt: timestamp('created_at').defaultNow(),
});
Let us break down what each piece does:
pgTable('users', {...})— Creates a table named "users" in PostgreSQL. If you are using MySQL, you would usemysqlTable. For SQLite,sqliteTable.serial('id').primaryKey()— An auto-incrementing number that uniquely identifies each row. Every table needs one.text('name').notNull()— A text column that cannot be empty. The string'name'is the actual column name in the database.varchar('email', { length: 255 }).unique()— A text column with a maximum length, where every value must be different..default('user')— If no value is provided, use this default..references(() => users.id)— This column points to theidcolumn in the users table. It is a foreign key — a link between tables.
Notice: this is all just TypeScript. Your editor gives you autocomplete, type checking, and error highlighting. No separate language to learn, no separate file format to understand.
2. Migrations: Keeping Your Database in Sync
Migrations are the process of updating your actual database to match your schema. When you add a new column to your schema file, the database does not magically know about it — you need to run a migration.
Drizzle uses a tool called drizzle-kit for this. The key commands:
# Generate migration files from your schema changes
npx drizzle-kit generate
# Apply pending migrations to the database
npx drizzle-kit migrate
# Push schema directly to the database (development shortcut)
npx drizzle-kit push
# Open Drizzle Studio (visual database browser)
npx drizzle-kit studio
The workflow is:
- Edit your schema file (
db/schema.ts) - Run
npx drizzle-kit generateto create a migration SQL file - Run
npx drizzle-kit migrateto apply it to your database
Or, during early development when you are experimenting, you can use npx drizzle-kit push to skip migration files and push the schema directly. This is faster but does not create a history of changes — so switch to generate + migrate before going to production.
You also need a drizzle.config.ts file to tell drizzle-kit where your schema is and how to connect:
// drizzle.config.ts
import { defineConfig } from 'drizzle-kit';
export default defineConfig({
schema: './db/schema.ts',
out: './drizzle', // where migration files go
dialect: 'postgresql',
dbCredentials: {
url: process.env.DATABASE_URL!,
},
});
3. Queries That Look Like SQL
This is where Drizzle really stands out. The query API reads almost like SQL translated into TypeScript. If you know what SELECT * FROM users WHERE role = 'admin' means, you can read Drizzle queries.
First, you set up the database connection:
// db/index.ts
import { drizzle } from 'drizzle-orm/node-postgres';
import * as schema from './schema';
export const db = drizzle(process.env.DATABASE_URL!, { schema });
Then you write queries:
import { db } from './db';
import { users, posts } from './db/schema';
import { eq, desc, and } from 'drizzle-orm';
// SELECT * FROM users;
const allUsers = await db.select().from(users);
// SELECT * FROM users WHERE role = 'admin';
const admins = await db.select().from(users)
.where(eq(users.role, 'admin'));
// SELECT * FROM posts WHERE published = true ORDER BY created_at DESC LIMIT 10;
const recentPosts = await db.select().from(posts)
.where(eq(posts.published, true))
.orderBy(desc(posts.createdAt))
.limit(10);
// SELECT name, email FROM users WHERE role = 'admin' AND name IS NOT NULL;
const adminNames = await db.select({
name: users.name,
email: users.email
})
.from(users)
.where(and(
eq(users.role, 'admin'),
isNotNull(users.name)
));
See the pattern? db.select().from(table).where(condition) maps directly to SELECT ... FROM table WHERE condition. You are learning SQL while using Drizzle, even if you do not realize it.
Code Examples: The Operations You Will See Most
When AI generates Drizzle code for your app, these are the operations that show up in almost every project.
Inserting data
// INSERT INTO users (name, email, role) VALUES ('Chuck', 'chuck@example.com', 'admin');
const newUser = await db.insert(users).values({
name: 'Chuck',
email: 'chuck@example.com',
role: 'admin',
}).returning(); // returns the created row with the auto-generated id
// Insert multiple rows at once
await db.insert(posts).values([
{ title: 'First Post', content: 'Hello world', authorId: 1 },
{ title: 'Second Post', content: 'Another one', authorId: 1 },
]);
The .returning() at the end is important — it tells the database to send back the row it just created, including the auto-generated id and any default values. Without it, you just get a confirmation that the insert happened but no data back.
Updating data
// UPDATE users SET role = 'moderator' WHERE email = 'chuck@example.com';
await db.update(users)
.set({ role: 'moderator' })
.where(eq(users.email, 'chuck@example.com'));
// UPDATE posts SET published = true WHERE author_id = 1;
await db.update(posts)
.set({ published: true })
.where(eq(posts.authorId, 1));
Deleting data
// DELETE FROM posts WHERE id = 5;
await db.delete(posts).where(eq(posts.id, 5));
// Delete and return what was deleted
const deleted = await db.delete(posts)
.where(eq(posts.published, false))
.returning();
Joining tables
// SELECT posts.title, users.name FROM posts
// INNER JOIN users ON posts.author_id = users.id
// WHERE posts.published = true;
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));
Every one of these has a direct SQL equivalent, which is shown in the comment above each query. This is not a coincidence — it is the whole point of Drizzle.
When AI Picks Drizzle Over Prisma
AI tools do not randomly pick ORMs. They evaluate the project context and choose what fits best. Here are the situations where AI will reach for Drizzle:
Edge and serverless deployments
If you mention Cloudflare Workers, Vercel Edge Functions, Deno Deploy, or any edge runtime, AI will almost always pick Drizzle. Prisma's binary engine does not run on most edge environments without special adapters. Drizzle runs everywhere JavaScript runs — no adapters needed.
Serverless databases
Databases like Neon, Turso, PlanetScale, and Vercel Postgres are designed for serverless apps. Drizzle has first-class drivers for all of them. When you tell AI to "use Neon for the database," Drizzle is the natural choice because the integration is seamless:
// Drizzle + Neon — that is the entire setup
import { neon } from '@neondatabase/serverless';
import { drizzle } from 'drizzle-orm/neon-http';
import * as schema from './schema';
const sql = neon(process.env.DATABASE_URL!);
export const db = drizzle(sql, { schema });
New, greenfield projects
When there is no existing database or ORM setup, AI often picks Drizzle for new TypeScript projects because the setup is simpler: fewer files, fewer commands, no code generation step. For a brand new project, Drizzle means less boilerplate.
When SQL control matters
If you ask AI to do something complex — raw queries, custom SQL functions, database-specific features — Drizzle gives more direct control. You can drop down to raw SQL at any point:
import { sql } from 'drizzle-orm';
// Run any raw SQL when the ORM does not have a built-in method
const result = await db.execute(
sql`SELECT COUNT(*) as total FROM posts WHERE published = true`
);
Bonus Concept: Drizzle Relations
One thing that trips people up: Drizzle has two ways to handle table relationships. The .references() you saw in the schema creates a foreign key in the database. But if you want to use Drizzle's relational query API (which looks more like Prisma), you need a separate relations configuration:
import { relations } from 'drizzle-orm';
import { users, posts } from './schema';
export const usersRelations = relations(users, ({ many }) => ({
posts: many(posts),
}));
export const postsRelations = relations(posts, ({ one }) => ({
author: one(users, {
fields: [posts.authorId],
references: [users.id],
}),
}));
With relations configured, you can use the relational query API:
// Fetch users with their posts — similar to Prisma's include
const usersWithPosts = await db.query.users.findMany({
with: {
posts: true,
},
});
This is optional. You can always use .innerJoin() and .leftJoin() instead. But if AI generates relational queries and they fail, the missing relations config is usually why.
What AI Gets Wrong About Drizzle
AI is remarkably good at generating Drizzle code, but it makes predictable mistakes. Here are the ones you will run into:
Missing relations configuration
This is the number one Drizzle error from AI. It defines the schema with .references() (which creates the database foreign key) but then uses the relational query API (db.query.users.findMany({ with: { posts: true } })) without defining the relations object. The foreign key exists in the database, but Drizzle's TypeScript layer does not know about the relationship.
Fix: If AI uses db.query with with:, make sure there is a relations configuration. If you see "Property does not exist" errors on relational queries, this is almost certainly the issue.
Wrong driver for the database
Drizzle uses different driver packages for different databases and connection methods. AI sometimes picks the wrong one:
- PostgreSQL locally:
drizzle-orm/node-postgreswithpg - Neon serverless:
drizzle-orm/neon-httpwith@neondatabase/serverless - Turso/LibSQL:
drizzle-orm/libsqlwith@libsql/client - Supabase:
drizzle-orm/postgres-jswithpostgres - MySQL:
drizzle-orm/mysql2withmysql2
If you get a connection error right at startup, check that the driver matches your actual database. AI sometimes uses the node-postgres driver when connecting to Neon, or the Neon driver when running locally.
Schema and migration sync issues
AI edits the schema file but does not tell you to run npx drizzle-kit generate and npx drizzle-kit migrate. Your TypeScript code compiles fine — the types match the schema file — but the actual database still has the old structure. You get runtime errors like "column does not exist."
Rule: Every schema change needs a migration. Schema file change → drizzle-kit generate → drizzle-kit migrate. No exceptions.
Mixing up pgTable column types
AI sometimes uses integer() where it should use serial() for auto-incrementing primary keys, or uses text() for dates instead of timestamp(). The code compiles, but the database behavior is wrong — like having to manually provide an ID for every insert.
Quick check: Primary keys should almost always be serial() (auto-incrementing integer) or uuid() (auto-generated unique string). Dates should be timestamp(), not text().
Forgetting .returning() on inserts
AI creates a record with db.insert(users).values({...}) and then tries to use the returned value — but without .returning(), the insert does not return any data. The variable is undefined and the next line crashes.
The Drizzle Debugging Checklist
When your Drizzle-powered app breaks: (1) Did you run drizzle-kit generate and drizzle-kit migrate after changing the schema? (2) Is the driver package correct for your database? (3) If using relational queries with with:, did you define the relations? (4) Is DATABASE_URL set in your .env? These four checks cover 90% of Drizzle errors.
The Drizzle Config File
Every Drizzle project needs a drizzle.config.ts file at the project root. This tells drizzle-kit where to find your schema, where to put migration files, and how to connect to your database. Here is the most common setup:
// drizzle.config.ts
import { defineConfig } from 'drizzle-kit';
export default defineConfig({
schema: './db/schema.ts', // where your table definitions live
out: './drizzle', // where migration SQL files are stored
dialect: 'postgresql', // 'postgresql', 'mysql', or 'sqlite'
dbCredentials: {
url: process.env.DATABASE_URL!,
},
});
If this file is missing or has the wrong dialect, drizzle-kit commands will fail. AI almost always generates this file, but double-check the dialect matches your actual database.
What to Learn Next
Drizzle is one piece of the database puzzle. These guides fill in the surrounding concepts:
- What Is an ORM? — The broader concept that Drizzle implements. Understand ORMs and you understand why tools like Drizzle and Prisma exist.
- What Is PostgreSQL? — The most popular database that Drizzle connects to. Learn what PostgreSQL is doing under the hood when Drizzle sends it queries.
- What Is SQL? — The language that Drizzle's query syntax is based on. Since Drizzle queries mirror SQL, learning SQL makes Drizzle code completely transparent.
- What Are Database Migrations? — The full explanation of how migrations work, why they matter, and what happens when they go wrong.
- What Is Express? — Drizzle pairs with Express in most backend projects. Understanding Express helps you see where Drizzle fits in the request-to-database flow.
- What Is Serverless? — Drizzle's zero-dependency, edge-ready design makes it the go-to ORM for serverless deployments. Learn why serverless changes how you think about databases.
- What Is Turso? — An edge database that pairs perfectly with Drizzle. If you want a database that's as lightweight as your ORM, Turso is worth knowing.
Next Step
Run npx drizzle-kit studio in any Drizzle project to open the visual database browser. It runs right in your browser and shows you every table, every column, and every row of data. It is the fastest way to verify that your schema matches what is actually in the database — and it is completely free.
FAQ
Drizzle ORM is a lightweight, TypeScript-first database tool that lets you write queries that look almost identical to raw SQL. It works with PostgreSQL, MySQL, and SQLite and is popular in AI-generated projects because it is fast, has zero dependencies, and runs everywhere — including edge environments like Cloudflare Workers and Vercel Edge Functions.
Prisma uses its own schema language and generates a client that abstracts away SQL. Drizzle defines schemas directly in TypeScript and writes queries that closely mirror SQL syntax. Drizzle is smaller, faster, has no code generation step, and works on edge runtimes. Prisma has a larger ecosystem, more documentation, and a visual studio tool. Choose Drizzle for lightweight, SQL-aware projects; Prisma for heavily abstracted, full-featured setups.
Yes, especially if you are building with AI. Drizzle has a smaller surface area than Prisma, fewer moving parts, and its queries read like plain SQL — which means the concepts transfer to any database tool. If your AI generates Drizzle code, you can often read it and understand what it does even without deep database experience.
You do not need to know SQL before starting, but Drizzle will teach you SQL as you use it. Because Drizzle queries mirror SQL structure — select, where, orderBy, limit — you naturally learn SQL patterns. This is actually an advantage: skills you pick up with Drizzle transfer directly to writing raw SQL if you ever need to.
Yes. Drizzle works with any PostgreSQL-compatible database, including Supabase, Neon, PlanetScale (MySQL), Turso (SQLite), and Vercel Postgres. You just need the right driver package — for example, drizzle-orm/neon-http for Neon or drizzle-orm/libsql for Turso. AI tools usually pick the correct driver automatically.