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
Generate Migration
When you change your schema, generate a migration:
npx drizzle-kit generateReview the SQL
Check the generated drizzle/ folder. Make sure it does what you expect.
Push to Database
Apply the migration:
npx drizzle-kit pushQuick 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 →