Drizzle ORM Guide (80-20)#
The 20% you need to know that covers 80% of use cases.
Installation#
PostgreSQL#
npm install drizzle-orm pg
npm install -D drizzle-kit @types/pg
MySQL#
npm install drizzle-orm mysql2
npm install -D drizzle-kit
SQLite#
npm install drizzle-orm better-sqlite3
npm install -D drizzle-kit @types/better-sqlite3
Project Setup#
Create drizzle.config.ts:
import { defineConfig } from 'drizzle-kit'
export default defineConfig({
schema: './src/db/schema.ts',
out: './drizzle',
dialect: 'postgresql', // or 'mysql', 'sqlite'
dbCredentials: {
url: process.env.DATABASE_URL!,
},
})
Define Schema#
// src/db/schema.ts
import { pgTable, serial, text, integer, timestamp, boolean } from 'drizzle-orm/pg-core'
export const users = pgTable('users', {
id: serial('id').primaryKey(),
name: text('name').notNull(),
email: text('email').notNull().unique(),
age: integer('age'),
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'),
userId: integer('user_id').references(() => users.id),
createdAt: timestamp('created_at').defaultNow(),
})
Database Connection#
PostgreSQL#
// 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 })
SQLite#
import { drizzle } from 'drizzle-orm/better-sqlite3'
import Database from 'better-sqlite3'
import * as schema from './schema'
const sqlite = new Database('sqlite.db')
export const db = drizzle(sqlite, { schema })
Migrations#
Generate Migration#
npx drizzle-kit generate
This creates SQL migration files in ./drizzle folder.
Run Migrations#
npx drizzle-kit migrate
Push Schema (Development)#
Skip migrations, directly push schema changes:
npx drizzle-kit push
Warning
Use push only in development. Use migrations in production.
CRUD Operations#
Insert#
import { db } from './db'
import { users } from './db/schema'
// Insert one
const newUser = await db.insert(users).values({
name: 'John Doe',
email: 'john@example.com',
age: 30,
}).returning()
console.log(newUser) // Returns inserted row
// Insert multiple
await db.insert(users).values([
{ name: 'Alice', email: 'alice@example.com' },
{ name: 'Bob', email: 'bob@example.com' },
])
Select#
import { eq, gt, and, or } from 'drizzle-orm'
// Select all
const allUsers = await db.select().from(users)
// Select specific columns
const names = await db.select({
name: users.name,
email: users.email,
}).from(users)
// Where clause
const activeUsers = await db
.select()
.from(users)
.where(eq(users.isActive, true))
// Multiple conditions
const filteredUsers = await db
.select()
.from(users)
.where(
and(
eq(users.isActive, true),
gt(users.age, 18)
)
)
// OR condition
const someUsers = await db
.select()
.from(users)
.where(
or(
eq(users.name, 'John'),
eq(users.name, 'Jane')
)
)
// Limit & Offset
const paginatedUsers = await db
.select()
.from(users)
.limit(10)
.offset(20)
// Order by
const sortedUsers = await db
.select()
.from(users)
.orderBy(users.createdAt)
Update#
// Update with condition
await db
.update(users)
.set({ age: 31 })
.where(eq(users.email, 'john@example.com'))
// Update and return
const updated = await db
.update(users)
.set({ isActive: false })
.where(eq(users.id, 1))
.returning()
Delete#
// Delete with condition
await db
.delete(users)
.where(eq(users.id, 1))
// Delete and return
const deleted = await db
.delete(users)
.where(eq(users.email, 'john@example.com'))
.returning()
Operators#
| Operator | Usage | SQL |
|---|---|---|
eq |
eq(users.id, 1) |
id = 1 |
ne |
ne(users.age, 30) |
age != 30 |
gt |
gt(users.age, 18) |
age > 18 |
gte |
gte(users.age, 18) |
age >= 18 |
lt |
lt(users.age, 65) |
age < 65 |
lte |
lte(users.age, 65) |
age <= 65 |
like |
like(users.name, '%John%') |
name LIKE '%John%' |
isNull |
isNull(users.age) |
age IS NULL |
isNotNull |
isNotNull(users.age) |
age IS NOT NULL |
and |
and(eq(...), gt(...)) |
AND |
or |
or(eq(...), eq(...)) |
OR |
Relationships#
Define Relations#
import { relations } from 'drizzle-orm'
export const usersRelations = relations(users, ({ many }) => ({
posts: many(posts),
}))
export const postsRelations = relations(posts, ({ one }) => ({
author: one(users, {
fields: [posts.userId],
references: [users.id],
}),
}))
Query with Joins#
// Manual join
const usersWithPosts = await db
.select({
user: users,
post: posts,
})
.from(users)
.leftJoin(posts, eq(users.id, posts.userId))
// With relations (requires schema)
const usersWithPosts = await db.query.users.findMany({
with: {
posts: true,
},
})
// Nested relations
const userWithPosts = await db.query.users.findFirst({
where: eq(users.id, 1),
with: {
posts: {
where: eq(posts.title, 'Hello'),
},
},
})
Column Types#
PostgreSQL#
import {
serial, integer, bigint, real, doublePrecision,
text, varchar, char, boolean,
date, timestamp, time, interval,
json, jsonb, uuid
} from 'drizzle-orm/pg-core'
export const example = pgTable('example', {
id: serial('id').primaryKey(),
num: integer('num'),
bigNum: bigint('big_num', { mode: 'number' }),
price: real('price'),
name: text('name'),
email: varchar('email', { length: 255 }),
isActive: boolean('is_active'),
birthDate: date('birth_date'),
createdAt: timestamp('created_at').defaultNow(),
metadata: jsonb('metadata'),
uuid: uuid('uuid').defaultRandom(),
})
Modifiers#
// Not null
name: text('name').notNull()
// Unique
email: text('email').unique()
// Default value
isActive: boolean('is_active').default(true)
createdAt: timestamp('created_at').defaultNow()
// Primary key
id: serial('id').primaryKey()
// Foreign key
userId: integer('user_id').references(() => users.id)
// On delete cascade
userId: integer('user_id').references(() => users.id, { onDelete: 'cascade' })
Transactions#
await db.transaction(async (tx) => {
const user = await tx.insert(users).values({
name: 'John',
email: 'john@example.com',
}).returning()
await tx.insert(posts).values({
title: 'First Post',
userId: user[0].id,
})
})
If any query fails, the entire transaction is rolled back.
Raw SQL#
import { sql } from 'drizzle-orm'
// Raw query
const result = await db.execute(
sql`SELECT * FROM users WHERE age > ${18}`
)
// With select
const users = await db
.select()
.from(users)
.where(sql`age > 18 AND name LIKE '%John%'`)
TypeScript Types#
import { InferSelectModel, InferInsertModel } from 'drizzle-orm'
import { users } from './schema'
// Infer types from schema
type User = InferSelectModel<typeof users>
type NewUser = InferInsertModel<typeof users>
// Usage
const user: User = {
id: 1,
name: 'John',
email: 'john@example.com',
age: 30,
isActive: true,
createdAt: new Date(),
}
const newUser: NewUser = {
name: 'Jane',
email: 'jane@example.com',
// Optional fields can be omitted
}
Best Practices#
- Use schema file - Keep all table definitions in one place
- Define relations - Makes queries easier
- Use migrations - Track database changes
- Type inference - Let TypeScript infer types from schema
- Transactions - Use for multiple related operations
- Indexes - Add for frequently queried columns
- Environment variables - Never hardcode database URLs
Example: Full Setup#
// 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!,
},
})
// src/db/schema.ts
import { pgTable, serial, text, timestamp, integer } from 'drizzle-orm/pg-core'
import { relations } from 'drizzle-orm'
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, { onDelete: 'cascade' }),
createdAt: timestamp('created_at').defaultNow(),
})
export const usersRelations = relations(users, ({ many }) => ({
posts: many(posts),
}))
export const postsRelations = relations(posts, ({ one }) => ({
author: one(users, {
fields: [posts.userId],
references: [users.id],
}),
}))
// 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 })
// src/index.ts
import { db } from './db'
import { users, posts } from './db/schema'
// Create user
const [user] = await db.insert(users).values({
name: 'John Doe',
email: 'john@example.com',
}).returning()
// Create post
await db.insert(posts).values({
title: 'Hello World',
content: 'My first post',
userId: user.id,
})
// Get user with posts
const userWithPosts = await db.query.users.findFirst({
where: (users, { eq }) => eq(users.id, user.id),
with: {
posts: true,
},
})
console.log(userWithPosts)
Quick Start
- Install:
npm install drizzle-orm pg drizzle-kit - Create schema file
- Run:
npx drizzle-kit generatethennpx drizzle-kit migrate - Start querying!