Back to Playbook
PlaybookDatabase

Drizzle ORM

Drizzle is a TypeScript ORM that gives you type-safe database queries. Think of it as a translator between your TypeScript code and SQL—write queries in code, get autocomplete, catch errors before runtime.

Why Drizzle?

Raw SQL strings are error-prone. Other ORMs are heavy and hide what's happening. Drizzle is the best of both worlds:

Type Safety

TypeScript knows your schema. Typo in a column name? Red squiggle before you run.

SQL-Like Syntax

Reads like SQL, so you understand what's actually running.

Lightweight

No heavy runtime. Fast cold starts—perfect for serverless (Vercel, Neon).

Migrations Built-In

Schema changes tracked and applied with drizzle-kit.

Defining Your Schema

Your schema lives in a TypeScript file—usually lib/db/schema.ts. Define tables with Drizzle's helpers:

// lib/db/schema.ts

import

{ pgTable, serial, text, timestamp, integer }

from

"drizzle-orm/pg-core"

;

export const users = pgTable("users", {

id: serial("id").primaryKey(),

name: text("name").notNull(),

email: text("email").notNull().unique(),

createdAt: timestamp("created_at").defaultNow(),

});

export const posts = pgTable("posts", {

id: serial("id").primaryKey(),

title: text("title").notNull(),

content: text("content"),

userId: integer("user_id").references(() => users.id),

createdAt: timestamp("created_at").defaultNow(),

});

SaucyTech Stack

We use Neon PostgreSQL, so use pgTable from drizzle-orm/pg-core. For MySQL or SQLite, import from their respective modules.

CRUD Operations

Create (Insert)

// Insert one user

const newUser = await db.insert(users).values({

name: "Alice",

email: "alice@example.com",

}).returning();

// Insert multiple

await db.insert(users).values([

{ name: "Bob", email: "bob@example.com" },

{ name: "Carol", email: "carol@example.com" },

]);

Read (Select)

// Get all users

const allUsers = await db.select().from(users);

// Get one by ID

const user = await db.select().from(users)

.where(eq(users.id, 1));

// Get specific columns

const names = await db.select({

name: users.name,

email: users.email,

}).from(users);

// With ordering and limit

const recent = await db.select().from(users)

.orderBy(desc(users.createdAt))

.limit(10);

Update

// Update a user

await db.update(users)

.set({ name: "Alice Smith" })

.where(eq(users.id, 1));

// Update with returning

const updated = await db.update(users)

.set({ name: "Alice Smith" })

.where(eq(users.id, 1))

.returning();

Delete

// Delete a user

await db.delete(users).where(eq(users.id, 1));

// Delete with returning (get deleted rows)

const deleted = await db.delete(users)

.where(eq(users.id, 1))

.returning();

Warning

Always include a .where() clause! Without it, you'll delete every row.

Joins & Relations

// Inner join: posts with their authors

const postsWithAuthors = await db

.select({

postTitle: posts.title,

authorName: users.name,

})

.from(posts)

.innerJoin(users, eq(posts.userId, users.id));

// Left join: all users, with their posts (if any)

const usersWithPosts = await db

.select()

.from(users)

.leftJoin(posts, eq(users.id, posts.userId));

Drizzle Relations (Optional)

For cleaner queries, define relations in your schema and use db.query for automatic eager loading. Check the Drizzle docs for the relations API.

Migrations with drizzle-kit

1

Generate Migration

When you change your schema, generate a migration:

npx drizzle-kit generate
2

Review the SQL

Check the generated drizzle/ folder. Make sure it does what you expect.

3

Push to Database

Apply the migration:

npx drizzle-kit push

Quick Sync

For development, npx drizzle-kit push syncs your schema directly without migration files. Great for rapid iteration.

Common Pitfalls

N+1 Query Problem

Looping through results and running a query for each row = slow.

Fix: Use joins or batch your queries. Fetch related data in one query, not N queries.

Forgetting to import operators

eq, desc, and, or must be imported.

Fix: import { eq, desc, and, or, like } from "drizzle-orm"

Schema out of sync with database

You changed the TypeScript schema but forgot to push the migration.

Fix: Run npx drizzle-kit push after schema changes. Use drizzle-kit studio to inspect your database.

Missing connection pooling

Serverless functions open many connections. Without pooling, you'll hit limits.

Fix: Neon has built-in pooling. Use the pooled connection string (the one with -pooler in the host).

Ready to connect your database?

Now that you know Drizzle, connect it to your Neon database.

Connect Database Guide →