TypeScript ORM for SQL databases with type-safe queries, schema definitions, and support for PostgreSQL, MySQL, SQLite, and more.
Comprehensive examples demonstrating practical usage patterns for Drizzle ORM.
Complete blog application with users, posts, comments, and tags.
import { pgTable, serial, text, integer, boolean, timestamp, relations, primaryKey } from 'drizzle-orm/pg-core';
export const users = pgTable('users', {
id: serial('id').primaryKey(),
name: text('name').notNull(),
email: text('email').notNull().unique(),
bio: text('bio'),
createdAt: timestamp('created_at').defaultNow().notNull(),
});
export const posts = pgTable('posts', {
id: serial('id').primaryKey(),
title: text('title').notNull(),
slug: text('slug').notNull().unique(),
content: text('content'),
published: boolean('published').default(false).notNull(),
authorId: integer('author_id').notNull(),
createdAt: timestamp('created_at').defaultNow().notNull(),
updatedAt: timestamp('updated_at').defaultNow().notNull(),
});
export const comments = pgTable('comments', {
id: serial('id').primaryKey(),
content: text('content').notNull(),
postId: integer('post_id').notNull(),
authorId: integer('author_id').notNull(),
createdAt: timestamp('created_at').defaultNow().notNull(),
});
export const tags = pgTable('tags', {
id: serial('id').primaryKey(),
name: text('name').notNull().unique(),
});
export const postsToTags = pgTable('posts_to_tags', {
postId: integer('post_id').notNull().references(() => posts.id),
tagId: integer('tag_id').notNull().references(() => tags.id),
}, (t) => ({
pk: primaryKey({ columns: [t.postId, t.tagId] }),
}));
// Relations
export const usersRelations = relations(users, ({ many }) => ({
posts: many(posts),
comments: many(comments),
}));
export const postsRelations = relations(posts, ({ one, many }) => ({
author: one(users, {
fields: [posts.authorId],
references: [users.id],
}),
comments: many(comments),
postsToTags: many(postsToTags),
}));
export const commentsRelations = relations(comments, ({ one }) => ({
post: one(posts, {
fields: [comments.postId],
references: [posts.id],
}),
author: one(users, {
fields: [comments.authorId],
references: [users.id],
}),
}));
export const tagsRelations = relations(tags, ({ many }) => ({
postsToTags: many(postsToTags),
}));
export const postsToTagsRelations = relations(postsToTags, ({ one }) => ({
post: one(posts, {
fields: [postsToTags.postId],
references: [posts.id],
}),
tag: one(tags, {
fields: [postsToTags.tagId],
references: [tags.id],
}),
}));import { eq, and, desc, sql } from 'drizzle-orm';
// Get blog post with all related data
async function getBlogPost(slug: string) {
return db.query.posts.findFirst({
where: and(
eq(posts.slug, slug),
eq(posts.published, true)
),
with: {
author: {
columns: {
id: true,
name: true,
bio: true,
},
},
comments: {
orderBy: desc(comments.createdAt),
with: {
author: {
columns: {
id: true,
name: true,
},
},
},
},
postsToTags: {
with: {
tag: true,
},
},
},
extras: {
readingTime: sql<number>`CEIL(LENGTH(${posts.content}) / 200.0)`.as('reading_time'),
},
});
}
// Get author's published posts
async function getAuthorPosts(authorId: number) {
return db.query.users.findFirst({
where: eq(users.id, authorId),
with: {
posts: {
where: eq(posts.published, true),
orderBy: desc(posts.createdAt),
columns: {
id: true,
title: true,
slug: true,
createdAt: true,
},
extras: {
commentCount: sql<number>`(
SELECT COUNT(*)
FROM ${comments}
WHERE ${comments.postId} = ${posts.id}
)`.as('comment_count'),
},
},
},
});
}
// Get recent posts with pagination
async function getRecentPosts(page: number, pageSize: number) {
return db.query.posts.findMany({
where: eq(posts.published, true),
orderBy: desc(posts.createdAt),
limit: pageSize,
offset: (page - 1) * pageSize,
with: {
author: {
columns: {
id: true,
name: true,
},
},
postsToTags: {
with: {
tag: true,
},
},
},
extras: {
excerpt: sql<string>`SUBSTRING(${posts.content}, 1, 200)`.as('excerpt'),
},
});
}
// Get posts by tag
async function getPostsByTag(tagName: string) {
const tag = await db.query.tags.findFirst({
where: eq(tags.name, tagName),
with: {
postsToTags: {
with: {
post: {
where: eq(posts.published, true),
with: {
author: {
columns: {
id: true,
name: true,
},
},
},
},
},
},
},
});
return tag?.postsToTags.map(ptt => ptt.post) ?? [];
}Complete e-commerce system with customers, orders, and products.
export const customers = pgTable('customers', {
id: serial('id').primaryKey(),
name: text('name').notNull(),
email: text('email').notNull().unique(),
createdAt: timestamp('created_at').defaultNow().notNull(),
});
export const orders = pgTable('orders', {
id: serial('id').primaryKey(),
customerId: integer('customer_id').notNull(),
status: text('status').notNull(),
total: numeric('total', { precision: 10, scale: 2 }).notNull(),
createdAt: timestamp('created_at').defaultNow().notNull(),
});
export const orderItems = pgTable('order_items', {
id: serial('id').primaryKey(),
orderId: integer('order_id').notNull(),
productId: integer('product_id').notNull(),
quantity: integer('quantity').notNull(),
price: numeric('price', { precision: 10, scale: 2 }).notNull(),
});
export const products = pgTable('products', {
id: serial('id').primaryKey(),
name: text('name').notNull(),
description: text('description'),
price: numeric('price', { precision: 10, scale: 2 }).notNull(),
stock: integer('stock').notNull(),
});// Get order with all details
async function getOrderDetails(orderId: number) {
return db.query.orders.findFirst({
where: eq(orders.id, orderId),
with: {
customer: {
columns: {
id: true,
name: true,
email: true,
},
},
items: {
with: {
product: true,
},
},
},
});
}
// Get customer order history
async function getCustomerOrders(customerId: number) {
return db.query.customers.findFirst({
where: eq(customers.id, customerId),
with: {
orders: {
orderBy: desc(orders.createdAt),
with: {
items: {
with: {
product: {
columns: {
id: true,
name: true,
},
},
},
},
},
},
},
});
}
// Get popular products
async function getPopularProducts(limit: number) {
return db.query.products.findMany({
limit,
extras: {
totalSold: sql<number>`(
SELECT COALESCE(SUM(${orderItems.quantity}), 0)
FROM ${orderItems}
WHERE ${orderItems.productId} = ${products.id}
)`.as('total_sold'),
},
orderBy: (fields) => desc(fields.totalSold),
});
}User authentication with sessions and roles.
export const users = pgTable('users', {
id: serial('id').primaryKey(),
email: text('email').notNull().unique(),
passwordHash: text('password_hash').notNull(),
emailVerified: boolean('email_verified').default(false).notNull(),
createdAt: timestamp('created_at').defaultNow().notNull(),
});
export const sessions = pgTable('sessions', {
id: text('id').primaryKey(),
userId: integer('user_id').notNull().references(() => users.id, { onDelete: 'cascade' }),
expiresAt: timestamp('expires_at').notNull(),
createdAt: timestamp('created_at').defaultNow().notNull(),
});
export const roles = pgTable('roles', {
id: serial('id').primaryKey(),
name: text('name').notNull().unique(),
});
export const userRoles = pgTable('user_roles', {
userId: integer('user_id').notNull().references(() => users.id),
roleId: integer('role_id').notNull().references(() => roles.id),
assignedAt: timestamp('assigned_at').defaultNow().notNull(),
}, (t) => ({
pk: primaryKey({ columns: [t.userId, t.roleId] }),
}));Aggregating data for analytics.
import { count, sum, avg, max, min, sql } from 'drizzle-orm';
// User statistics
async function getUserStats() {
return db.select({
totalUsers: count(),
verifiedUsers: count(users.emailVerified),
newUsersToday: sql<number>`COUNT(*) FILTER (WHERE ${users.createdAt} > CURRENT_DATE)`,
}).from(users);
}
// Post analytics
async function getPostAnalytics() {
return db.select({
authorId: posts.authorId,
totalPosts: count(posts.id),
avgLength: avg(sql`LENGTH(${posts.content})`),
latestPost: max(posts.createdAt),
})
.from(posts)
.groupBy(posts.authorId)
.having(gt(count(posts.id), 5));
}Install with Tessl CLI
npx tessl i tessl/npm-drizzle-orm