webdev.complete
🧬 ORMs: Prisma & Drizzle
🟢The Backend
Lesson 74 of 117
30 min

Drizzle ORM

TS-first schema, SQL-like, edge-friendly, lighter bundle.

Drizzle is the ORM that feels like raw SQL but gives you full type safety on top. No DSL to memorize, no codegen step, no "black box" query engine. It is the default choice for new edge-deployed TypeScript projects in 2026 because it bundles tiny, runs anywhere, and reads almost like the SQL you would have written by hand.

Install

bash
pnpm add drizzle-orm pg
pnpm add -D drizzle-kit @types/pg

Define schema in TypeScript

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

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

export const posts = pgTable("posts", {
  id: serial("id").primaryKey(),
  authorId: integer("author_id").references(() => users.id),
  title: text("title").notNull(),
  body: text("body"),
  published: boolean("published").default(false).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] }),
}));

Connect the database

src/db/index.ts
import { drizzle } from "drizzle-orm/node-postgres";
import { Pool } from "pg";
import * as schema from "./schema";

const pool = new Pool({ connectionString: process.env.DATABASE_URL });
export const db = drizzle(pool, { schema });

Edge runtimes use the HTTP driver instead:

ts
import { drizzle } from "drizzle-orm/neon-http";
import { neon } from "@neondatabase/serverless";

const sql = neon(process.env.DATABASE_URL!);
export const db = drizzle(sql);

Query: two flavors

Drizzle gives you a query builder that looks like SQL and a relational API that looks like Prisma. Pick whichever fits the moment.

Query builder (SQL-shaped)

ts
import { eq, and, desc } from "drizzle-orm";
import { db } from "@/db";
import { posts, users } from "@/db/schema";

// SELECT * FROM posts WHERE published = true ORDER BY id DESC LIMIT 10
const recent = await db
  .select()
  .from(posts)
  .where(eq(posts.published, true))
  .orderBy(desc(posts.id))
  .limit(10);

// JOIN
const rows = await db
  .select({
    postId: posts.id,
    title: posts.title,
    authorName: users.name,
  })
  .from(posts)
  .innerJoin(users, eq(users.id, posts.authorId))
  .where(eq(posts.published, true));

Relational query API

ts
const user = await db.query.users.findFirst({
  where: (u, { eq }) => eq(u.email, "ada@example.com"),
  with: { posts: { where: (p, { eq }) => eq(p.published, true) } },
});

// user is fully typed: { id, email, name, createdAt, posts: Post[] }

Inserts, updates, deletes

ts
const [user] = await db.insert(users).values({
  email: "ada@example.com",
  name: "Ada",
}).returning();

await db.update(users)
  .set({ name: "Ada Lovelace" })
  .where(eq(users.id, user.id));

await db.delete(posts).where(eq(posts.authorId, user.id));

Migrations with drizzle-kit

drizzle.config.ts
import { defineConfig } from "drizzle-kit";

export default defineConfig({
  schema: "./src/db/schema.ts",
  out: "./drizzle",
  dialect: "postgresql",
  dbCredentials: { url: process.env.DATABASE_URL! },
});
bash
# generate SQL migration from schema changes
pnpm drizzle-kit generate

# apply to dev DB
pnpm drizzle-kit migrate

# fast iterate without migrations (dev only)
pnpm drizzle-kit push

# inspect data
pnpm drizzle-kit studio
Generate vs push
generate creates a versioned SQL file you commit and run in CI. push diffs your schema directly to the DB without a migration file. Use generate for production, push for fast iteration.

Transactions

ts
await db.transaction(async (tx) => {
  const [order] = await tx.insert(orders).values({ userId, total }).returning();
  await tx.insert(orderItems).values(items.map(i => ({ ...i, orderId: order.id })));
});

Why teams pick Drizzle

  • Edge-friendly. Tiny bundle, no runtime engine, works in Cloudflare Workers and Vercel Edge.
  • SQL fluency stays useful. Knowing SQL helps you write queries; with Prisma you had to learn its DSL.
  • Zero codegen step for types. Schema = source of truth.
  • Open-source migration tool. No proprietary cloud step required.

Where Drizzle hurts a bit

  • Less batteries than Prisma (no Studio with magic forms, no Accelerate equivalent that ships in the box).
  • The relational API is newer and sometimes lags behind the query builder in features.
  • More boilerplate when defining relations.

Quiz

Quiz1 / 3

What is the main practical advantage of Drizzle over Prisma in 2026?

Recap

  • Schema is a plain TS file. No DSL.
  • Two query styles: SQL-shaped builder + relational findX API.
  • drizzle-kit: generate, migrate, push, studio.
  • Edge-ready, tiny bundle, no codegen.
  • Pick Drizzle for edge, performance, and SQL fluency.
Built with Next.js, Tailwind & Sandpack.
Learn. Build. Ship.