| name | typescript-drizzle-orm |
| description | Type-safe SQL with Drizzle ORM in TypeScript. Use when defining database schemas, writing queries, setting up relations, running migrations, or working with PostgreSQL/MySQL/SQLite/Cloudflare D1/Durable Objects data layers. |
| user-invocable | false |
Drizzle ORM
Lightweight, type-safe ORM with SQL-like and relational query APIs for PostgreSQL, MySQL, SQLite, Cloudflare D1, and Durable Objects.
Quick Start (PostgreSQL)
import {
pgTable,
serial,
text,
integer,
timestamp,
boolean,
varchar,
uuid,
primaryKey,
unique,
index
} 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(),
age: integer('age'),
isActive: boolean('is_active').default(true),
createdAt: timestamp('created_at').defaultNow().notNull(),
updatedAt: timestamp('updated_at').$onUpdate(() => new Date()),
})
export const posts = pgTable('posts', {
id: serial('id').primaryKey(),
title: text('title').notNull(),
content: text('content'),
authorId: integer('author_id')
.notNull()
.references(() => users.id, { onDelete: 'cascade' }),
createdAt: timestamp('created_at').defaultNow().notNull(),
})
See references/postgresql.md for detailed PostgreSQL patterns.
Quick Start (SQLite/D1)
import { sqliteTable, text, integer } from 'drizzle-orm/sqlite-core'
export const users = sqliteTable('users', {
id: text('id').primaryKey(),
name: text('name').notNull(),
email: text('email').notNull(),
isActive: integer('is_active', { mode: 'boolean' }).default(true),
createdAt: text('created_at').notNull(),
})
export const posts = sqliteTable('posts', {
id: text('id').primaryKey(),
title: text('title').notNull(),
content: text('content'),
authorId: text('author_id')
.notNull()
.references(() => users.id, { onDelete: 'cascade' }),
createdAt: text('created_at').notNull(),
})
See references/sqlite.md for SQLite patterns and references/cloudflare.md for D1 and Durable Objects.
Type Inference
export type User = typeof users.$inferSelect
export type NewUser = typeof users.$inferInsert
export type Post = typeof posts.$inferSelect
export type NewPost = typeof posts.$inferInsert
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.authorId],
references: [users.id],
}),
}))
SQL-like Queries
import { eq, and, or, gt, like, isNull, desc, asc } from 'drizzle-orm'
const allUsers = await db.select().from(users)
const names = await db.select({ name: users.name }).from(users)
const activeUsers = await db
.select()
.from(users)
.where(eq(users.isActive, true))
const filtered = await db
.select()
.from(users)
.where(and(
eq(users.isActive, true),
gt(users.age, 18)
))
const matching = await db
.select()
.from(users)
.where(like(users.email, '%@example.com'))
const recent = await db
.select()
.from(posts)
.orderBy(desc(posts.createdAt))
.limit(10)
const postsWithAuthors = await db
.select({
postTitle: posts.title,
authorName: users.name,
})
.from(posts)
.leftJoin(users, eq(posts.authorId, users.id))
Relational Queries
const db = drizzle(pool, { schema })
const usersWithPosts = await db.query.users.findMany({
with: {
posts: true,
},
})
const partial = await db.query.users.findMany({
columns: {
id: true,
name: true,
},
with: {
posts: {
columns: {
title: true,
createdAt: true,
},
},
},
})
const user = await db.query.users.findFirst({
where: eq(users.id, 1),
with: { posts: true },
})
const withoutEmail = await db.query.users.findMany({
columns: {
email: false,
},
})
Insert
const [newUser] = await db
.insert(users)
.values({ name: 'Alice', email: 'alice@example.com' })
.returning()
await db.insert(users).values([
{ name: 'Bob', email: 'bob@example.com' },
{ name: 'Carol', email: 'carol@example.com' },
])
await db
.insert(users)
.values({ name: 'Alice', email: 'alice@example.com' })
.onConflictDoUpdate({
target: users.email,
set: { name: 'Alice Updated' },
})
Update
await db
.update(users)
.set({ isActive: false })
.where(eq(users.id, 1))
const [updated] = await db
.update(users)
.set({ name: 'New Name' })
.where(eq(users.id, 1))
.returning()
Delete
await db.delete(users).where(eq(users.id, 1))
const [deleted] = await db
.delete(users)
.where(eq(users.id, 1))
.returning()
Transactions
await db.transaction(async (tx) => {
const [user] = await tx
.insert(users)
.values({ name: 'Alice', email: 'alice@example.com' })
.returning()
await tx.insert(posts).values({
title: 'First Post',
authorId: user.id,
})
})
Entity Pattern
Domain entities encapsulate data transformations between API, domain, and database layers.
import type { InferInsertModel, InferSelectModel } from 'drizzle-orm'
import type { users } from './schema'
type UserRecord = InferSelectModel<typeof users>
type UserInsert = InferInsertModel<typeof users>
class UserEntity {
public readonly id: string
public readonly name: string
public readonly email: string
public readonly createdAt: Date
private constructor(data: UserEntityData) {
Object.assign(this, data)
}
static fromRequest(rq: CreateUserRequest, id?: string): UserEntity {
return new UserEntity({
id: id ?? crypto.randomUUID(),
name: rq.name,
email: rq.email,
createdAt: new Date(),
})
}
static fromRecord(record: UserRecord): UserEntity {
return new UserEntity({
id: record.id,
name: record.name,
email: record.email,
createdAt: record.createdAt,
})
}
toRecord(): UserInsert {
return {
id: this.id,
name: this.name,
email: this.email,
createdAt: this.createdAt,
}
}
toResponse(): UserResponse {
return {
id: this.id,
name: this.name,
email: this.email,
createdAt: this.createdAt.toISOString(),
}
}
}
See references/entity-pattern.md for detailed examples.
Repository Pattern
Repositories wrap database access with error handling and business logic.
import { eq, and } from 'drizzle-orm'
import { users } from './schema'
import { UserEntity } from './entities/UserEntity'
class UserRepo {
constructor(private db: DrizzleDB) {}
async getById(id: string): Promise<UserEntity> {
const record = await this.db.query.users.findFirst({
where: eq(users.id, id),
})
if (!record) throw new NotFoundError('User not found')
return UserEntity.fromRecord(record)
}
async create(entity: UserEntity): Promise<UserEntity> {
try {
const [record] = await this.db
.insert(users)
.values(entity.toRecord())
.returning()
return UserEntity.fromRecord(record)
} catch (error) {
throw handleDBError(error, { userId: entity.id })
}
}
async update(entity: UserEntity): Promise<UserEntity> {
const [record] = await this.db
.update(users)
.set(entity.toRecord())
.where(eq(users.id, entity.id))
.returning()
if (!record) throw new NotFoundError('User not found')
return UserEntity.fromRecord(record)
}
}
See references/repository-pattern.md for detailed examples.
Database-Specific Guides
For database-specific patterns, connection setup, migrations, and testing:
Guidelines
- Define schema in dedicated
schema.ts file(s)
- Use
$inferSelect and $inferInsert for types - don't duplicate
- Always define relations for nested queries with
db.query
- Pass
{ schema } to drizzle() to enable relational queries
- Use SQL-like API (
db.select()) for complex joins
- Use relational API (
db.query) for nested data fetching
- Foreign keys need explicit
references(() => table.column)
- Use
returning() to get inserted/updated/deleted rows
- Wrap database access in Repository classes for error handling
- Use Entity classes for all data transformations (fromRequest, fromRecord, toRecord, toResponse)
- Add
lockVersion column for optimistic locking on mutable resources
- Handle DB errors with specific error types (23505=conflict, 23503=not found, 40001/OC000=retry)