🎯 Ejemplos recomendados
Balanced sample collections from various categories for you to explore
Ejemplos de ORM Drizzle
Ejemplos de ORM Drizzle ligero en TypeScript incluyendo definición de esquemas, consultas, migraciones, relaciones y patrones de base de datos
💻 Configuración Básica de Drizzle ORM typescript
🟢 simple
⭐⭐
Configuración básica de Drizzle ORM incluyendo configuración de base de datos, definición de esquema y operaciones CRUD básicas
⏱️ 30 min
🏷️ drizzle, orm, typescript, database
Prerequisites:
TypeScript, SQL basics, Node.js, Database concepts
// Drizzle ORM Basic Setup Example
import { drizzle } from 'drizzle-orm/postgres-js';
import { pgTable, serial, text, integer, timestamp, boolean, varchar, pgEnum } from 'drizzle-orm/pg-core';
import { relations } from 'drizzle-orm';
import { sql } from 'drizzle-orm';
// 1. Database Configuration
// Environment variables
const DATABASE_URL = process.env.DATABASE_URL || 'postgresql://localhost:5432/myapp';
// Database instance
export const db = drizzle(DATABASE_URL);
// 2. Enums
export const userRoleEnum = pgEnum('user_role', ['admin', 'user', 'moderator']);
export const postStatusEnum = pgEnum('post_status', ['draft', 'published', 'archived']);
export const orderStatusEnum = pgEnum('order_status', ['pending', 'confirmed', 'shipped', 'delivered', 'cancelled']);
// 3. Table Definitions
// Users table
export const users = pgTable('users', {
id: serial('id').primaryKey(),
username: varchar('username', { length: 50 }).unique().notNull(),
email: varchar('email', { length: 255 }).unique().notNull(),
passwordHash: varchar('password_hash', { length: 255 }).notNull(),
firstName: varchar('first_name', { length: 100 }).notNull(),
lastName: varchar('last_name', { length: 100 }).notNull(),
role: userRoleEnum('role').default('user'),
isActive: boolean('is_active').default(true),
emailVerified: boolean('email_verified').default(false),
avatarUrl: varchar('avatar_url', { length: 500 }).optional(),
bio: text('bio').optional(),
createdAt: timestamp('created_at', { mode: 'default' }).defaultNow(),
updatedAt: timestamp('updated_at', { mode: 'default' }).defaultNow(),
lastLoginAt: timestamp('last_login_at').optional()
});
// Categories table
export const categories = pgTable('categories', {
id: serial('id').primaryKey(),
name: varchar('name', { length: 100 }).notNull(),
slug: varchar('slug', { length: 100 }).unique().notNull(),
description: text('description').optional(),
parentId: integer('parent_id').references(() => categories.id).optional(),
isActive: boolean('is_active').default(true),
sortOrder: integer('sort_order').default(0),
createdAt: timestamp('created_at', { mode: 'default' }).defaultNow()
});
// Posts table
export const posts = pgTable('posts', {
id: serial('id').primaryKey(),
title: varchar('title', { length: 255 }).notNull(),
slug: varchar('slug', { length: 255 }).unique().notNull(),
content: text('content').notNull(),
excerpt: text('excerpt').optional(),
authorId: integer('author_id').references(() => users.id).notNull(),
status: postStatusEnum('status').default('draft'),
featured: boolean('featured').default(false),
viewCount: integer('view_count').default(0),
publishedAt: timestamp('published_at').optional(),
createdAt: timestamp('created_at', { mode: 'default' }).defaultNow(),
updatedAt: timestamp('updated_at', { mode: 'default' }).defaultNow()
});
// Post categories join table
export const postCategories = pgTable('post_categories', {
postId: integer('post_id').references(() => posts.id).notNull(),
categoryId: integer('category_id').references(() => categories.id).notNull(),
createdAt: timestamp('created_at', { mode: 'default' }).defaultNow()
});
// Comments table
export const comments = pgTable('comments', {
id: serial('id').primaryKey(),
postId: integer('post_id').references(() => posts.id).notNull(),
authorId: integer('author_id').references(() => users.id).notNull(),
content: text('content').notNull(),
parentId: integer('parent_id').references(() => comments.id).optional(),
isApproved: boolean('is_approved').default(false),
createdAt: timestamp('created_at', { mode: 'default' }).defaultNow(),
updatedAt: timestamp('updated_at', { mode: 'default' }).defaultNow()
});
// Tags table
export const tags = pgTable('tags', {
id: serial('id').primaryKey(),
name: varchar('name', { length: 50 }).unique().notNull(),
slug: varchar('slug', { length: 50 }).unique().notNull(),
color: varchar('color', { length: 7 }).default('#000000'), // Hex color
createdAt: timestamp('created_at', { mode: 'default' }).defaultNow()
});
// Post tags join table
export const postTags = pgTable('post_tags', {
postId: integer('post_id').references(() => posts.id).notNull(),
tagId: integer('tag_id').references(() => tags.id).notNull(),
createdAt: timestamp('created_at', { mode: 'default' }).defaultNow()
});
// 4. Type Definitions (TypeScript types extracted from schemas)
export type User = typeof users.$inferSelect;
export type NewUser = typeof users.$inferInsert;
export type Category = typeof categories.$inferSelect;
export type NewCategory = typeof categories.$inferInsert;
export type Post = typeof posts.$inferSelect;
export type NewPost = typeof posts.$inferInsert;
export type Comment = typeof comments.$inferSelect;
export type NewComment = typeof comments.$inferInsert;
export type Tag = typeof tags.$inferSelect;
export type NewTag = typeof tags.$inferInsert;
// 5. Relations Definition
export const usersRelations = relations(users, ({ many }) => ({
posts: many(posts, { relationName: 'author' }),
comments: many(comments, { relationName: 'author' })
}));
export const categoriesRelations = relations(categories, ({ many, one }) => ({
parent: one(categories, {
fields: [categories.parentId],
references: [categories.id],
relationName: 'children'
}),
children: many(categories, {
fields: [categories.parentId],
references: [categories.id],
relationName: 'parent'
}),
posts: many(posts, { relationName: 'categories' })
}));
export const postsRelations = relations(posts, ({ one, many }) => ({
author: one(users, {
fields: [posts.authorId],
references: [users.id],
relationName: 'posts'
}),
comments: many(comments, { relationName: 'post' }),
categories: many(categories, { relationName: 'posts' }),
tags: many(tags, { relationName: 'posts' }),
parentComments: many(comments, {
fields: [comments.parentId],
references: [comments.id],
relationName: 'childComments'
})
}));
export const commentsRelations = relations(comments, ({ one, many }) => ({
post: one(posts, {
fields: [comments.postId],
references: [posts.id],
relationName: 'comments'
}),
author: one(users, {
fields: [comments.authorId],
references: [users.id],
relationName: 'comments'
}),
parent: one(comments, {
fields: [comments.parentId],
references: [comments.id],
relationName: 'childComments'
}),
childComments: many(comments, {
fields: [comments.parentId],
references: [comments.id],
relationName: 'parent'
})
}));
export const tagsRelations = relations(tags, ({ many }) => ({
posts: many(posts, { relationName: 'tags' })
}));
// 6. Basic CRUD Operations
// Create operations
export async function createUser(userData: NewUser): Promise<User> {
const [user] = await db.insert(users).values(userData).returning();
return user;
}
export async function createCategory(categoryData: NewCategory): Promise<Category> {
const [category] = await db.insert(categories).values(categoryData).returning();
return category;
}
export async function createPost(postData: NewPost, categoryIds?: number[], tagIds?: number[]): Promise<Post> {
const [post] = await db.insert(posts).values(postData).returning();
// Add categories if provided
if (categoryIds && categoryIds.length > 0) {
await db.insert(postCategories).values(
categoryIds.map(categoryId => ({
postId: post.id,
categoryId
}))
);
}
// Add tags if provided
if (tagIds && tagIds.length > 0) {
await db.insert(postTags).values(
tagIds.map(tagId => ({
postId: post.id,
tagId
}))
);
}
return post;
}
export async function createComment(commentData: NewComment): Promise<Comment> {
const [comment] = await db.insert(comments).values(commentData).returning();
return comment;
}
export async function createTag(tagData: NewTag): Promise<Tag> {
const [tag] = await db.insert(tags).values(tagData).returning();
return tag;
}
// Read operations
export async function getUserById(id: number): Promise<User | undefined> {
const [user] = await db.select().from(users).where(eq(users.id, id)).limit(1);
return user;
}
export async function getUserByEmail(email: string): Promise<User | undefined> {
const [user] = await db.select().from(users).where(eq(users.email, email)).limit(1);
return user;
}
export function getAllUsers(): Promise<User[]> {
return db.select().from(users);
}
export function getActiveUsers(): Promise<User[]> {
return db.select().from(users).where(eq(users.isActive, true));
}
export async function getPostById(id: number): Promise<Post | undefined> {
const [post] = await db.select().from(posts).where(eq(posts.id, id)).limit(1);
return post;
}
export function getPostsByAuthor(authorId: number): Promise<Post[]> {
return db.select().from(posts).where(eq(posts.authorId, authorId));
}
export function getPublishedPosts(): Promise<Post[]> {
return db.select().from(posts).where(eq(posts.status, 'published'));
}
export function getFeaturedPosts(): Promise<Post[]> {
return db.select().from(posts).where(eq(posts.featured, true));
}
export function searchPosts(searchTerm: string): Promise<Post[]> {
return db.select().from(posts).where(
or(
ilike(posts.title, `%${searchTerm}%`),
ilike(posts.content, `%${searchTerm}%`)
)
);
}
export function getPostsWithCategories(postId: number): Promise<Post & { categories: Category[] }> {
return db
.select({
id: posts.id,
title: posts.title,
slug: posts.slug,
content: posts.content,
excerpt: posts.excerpt,
authorId: posts.authorId,
status: posts.status,
featured: posts.featured,
viewCount: posts.viewCount,
publishedAt: posts.publishedAt,
createdAt: posts.createdAt,
updatedAt: posts.updatedAt
})
.from(posts)
.leftJoin(postCategories, eq(posts.id, postCategories.postId))
.leftJoin(categories, eq(postCategories.categoryId, categories.id))
.where(eq(posts.id, postId))
.groupBy(posts.id)
.then(posts => {
// This is a simplified version - in real code, you'd use more sophisticated SQL
return posts.map(post => ({
...post,
categories: [] // Would need to fetch categories separately or use proper SQL
}));
});
}
// Update operations
export async function updateUser(id: number, updateData: Partial<NewUser>): Promise<User> {
const [user] = await db
.update(users)
.set({ ...updateData, updatedAt: new Date() })
.where(eq(users.id, id))
.returning();
return user;
}
export async function updatePost(id: number, updateData: Partial<NewPost>): Promise<Post> {
const [post] = await db
.update(posts)
.set({ ...updateData, updatedAt: new Date() })
.where(eq(posts.id, id))
.returning();
return post;
}
export async function incrementPostViewCount(id: number): Promise<Post> {
const [post] = await db
.update(posts)
.set({
viewCount: sql`${posts.viewCount} + 1`,
updatedAt: new Date()
})
.where(eq(posts.id, id))
.returning();
return post;
}
// Delete operations
export async function deleteUser(id: number): Promise<void> {
await db.delete(users).where(eq(users.id, id));
}
export async function deletePost(id: number): Promise<void> {
await db.delete(posts).where(eq(posts.id, id));
}
export async function deleteComment(id: number): Promise<void> {
await db.delete(comments).where(eq(comments.id, id));
}
// 7. Advanced Queries
export function getUsersWithPostCount() {
return db
.select({
id: users.id,
username: users.username,
email: users.email,
firstName: users.firstName,
lastName: users.lastName,
role: users.role,
isActive: users.isActive,
postCount: count(posts.id)
})
.from(users)
.leftJoin(posts, eq(users.id, posts.authorId))
.groupBy(users.id)
.orderBy(desc(sql`count(${posts.id})`));
}
export function getRecentPosts(limit: number = 10) {
return db
.select()
.from(posts)
.where(eq(posts.status, 'published'))
.orderBy(desc(posts.createdAt))
.limit(limit);
}
export function getPopularPosts(days: number = 30, limit: number = 5) {
return db
.select()
.from(posts)
.where(
and(
eq(posts.status, 'published'),
gte(posts.createdAt, new Date(Date.now() - days * 24 * 60 * 60 * 1000))
)
)
.orderBy(desc(posts.viewCount))
.limit(limit);
}
export function getPostsWithComments(postId: number) {
return db.query.posts
.findFirst({
where: eq(posts.id, postId),
with: {
author: {
columns: {
id: true,
username: true,
firstName: true,
lastName: true
}
},
comments: {
orderBy: desc(comments.createdAt),
with: {
author: {
columns: {
id: true,
username: true,
firstName: true,
lastName: true
}
}
}
}
}
});
}
// 8. Transaction Examples
export async function createPostWithTags(
postData: NewPost,
tagNames: string[]
): Promise<Post> {
return db.transaction(async (tx) => {
// Create the post
const [post] = await tx.insert(posts).values(postData).returning();
// Create or find tags
const createdTags = await Promise.all(
tagNames.map(async (tagName) => {
// Check if tag already exists
const [existingTag] = await tx
.select()
.from(tags)
.where(eq(tags.name, tagName))
.limit(1);
if (existingTag) {
return existingTag;
}
// Create new tag
const [newTag] = await tx
.insert(tags)
.values({
name: tagName,
slug: tagName.toLowerCase().replace(/\s+/g, '-')
})
.returning();
return newTag;
})
);
// Create post-tag relationships
await tx.insert(postTags).values(
createdTags.map(tag => ({
postId: post.id,
tagId: tag.id
}))
);
return post;
});
}
export async function transferPostOwnership(
postId: number,
newAuthorId: number
): Promise<Post> {
return db.transaction(async (tx) => {
// Update the post
const [post] = await tx
.update(posts)
.set({ authorId: newAuthorId, updatedAt: new Date() })
.where(eq(posts.id, postId))
.returning();
return post;
});
}
// 9. Migration Helper Functions
export async function createDatabaseSchema() {
// This would typically be handled by Drizzle migrations
// but here's an example of programmatic schema creation
console.log('Creating database schema...');
// Tables would be created here if not using migration files
console.log('Database schema created successfully!');
}
// 10. Database Health Check
export async function checkDatabaseHealth(): Promise<boolean> {
try {
// Test basic connection
await db.select().from(users).limit(1);
// Test all tables exist
const tables = [
'users',
'categories',
'posts',
'comments',
'tags',
'post_categories',
'post_tags'
];
for (const table of tables) {
await db.select().from(sql.raw(`SELECT 1 FROM ${table} LIMIT 1`));
}
console.log('Database health check passed');
return true;
} catch (error) {
console.error('Database health check failed:', error);
return false;
}
}
// 11. Utility Functions
// Type-safe query builder helper
function buildQuery<T>(
table: any,
options: {
where?: any;
orderBy?: any;
limit?: number;
offset?: number;
select?: any[];
} = {}
) {
let query = db.select(options.select || []).from(table);
if (options.where) {
query = query.where(options.where);
}
if (options.orderBy) {
query = query.orderBy(options.orderBy);
}
if (options.limit) {
query = query.limit(options.limit);
}
if (options.offset) {
query = query.offset(options.offset);
}
return query as any; // Return as T
}
// Pagination helper
export interface PaginationOptions {
page?: number;
limit?: number;
sortBy?: string;
sortOrder?: 'asc' | 'desc';
}
export interface PaginatedResult<T> {
data: T[];
pagination: {
page: number;
limit: number;
total: number;
totalPages: number;
hasNext: boolean;
hasPrev: boolean;
};
}
export async function getPaginatedResults<T>(
query: any,
options: PaginationOptions = {}
): Promise<PaginatedResult<T>> {
const page = options.page || 1;
const limit = options.limit || 20;
const offset = (page - 1) * limit;
// Get total count
const [{ count }] = await db
.select({ count: sql`count(*)` })
.from(query as any);
const total = Number(count);
// Get paginated data
const data = await query
.limit(limit)
.offset(offset)
.orderBy(
options.sortBy && options.sortOrder
? options.sortOrder === 'asc'
? asc(sql.raw(`${options.sortBy}`))
: desc(sql.raw(`${options.sortBy}`))
: sql`id`
);
return {
data,
pagination: {
page,
limit,
total,
totalPages: Math.ceil(total / limit),
hasNext: page * limit < total,
hasPrev: page > 1
}
};
}
// Import required operators and functions
import { eq, and, or, gte, lte, ilike, count, asc, desc } from 'drizzle-orm';
export {
users,
categories,
posts,
comments,
tags,
postCategories,
postTags,
db,
type User,
type NewUser,
type Category,
type NewCategory,
type Post,
type NewPost,
type Comment,
type NewComment,
type Tag,
type NewTag
};
💻 Migraciones y Relaciones de Drizzle typescript
🟡 intermediate
⭐⭐⭐⭐
Características avanzadas de Drizzle ORM incluyendo migraciones, relaciones complejas, carga ansiosa y patrones de base de datos
⏱️ 45 min
🏷️ drizzle, migrations, relations, typescript
Prerequisites:
Drizzle basics, TypeScript, SQL, Database design, PostgreSQL
// Drizzle Migrations and Relations Advanced Example
import { drizzle } from 'drizzle-orm/postgres-js';
import { pgTable, serial, text, integer, timestamp, boolean, varchar, pgEnum, json, real } from 'drizzle-orm/pg-core';
import { relations } from 'drizzle-orm';
import { sql } from 'drizzle-orm';
import { migrate } from 'drizzle-orm/postgres-js/migrator';
import { eq, and, or, inArray, isNull, not, asc, desc } from 'drizzle-orm';
// Database configuration
const DATABASE_URL = process.env.DATABASE_URL || 'postgresql://localhost:5432/myapp';
export const db = drizzle(DATABASE_URL);
// 1. Enhanced Table Definitions with Advanced Features
// Payment status enum
export const paymentStatusEnum = pgEnum('payment_status', [
'pending',
'processing',
'completed',
'failed',
'refunded'
]);
// Notification type enum
export const notificationTypeEnum = pgEnum('notification_type', [
'info',
'success',
'warning',
'error'
]);
// Users table with additional fields
export const users = pgTable('users', {
id: serial('id').primaryKey(),
username: varchar('username', { length: 50 }).unique().notNull(),
email: varchar('email', { length: 255 }).unique().notNull(),
passwordHash: varchar('password_hash', { length: 255 }).notNull(),
firstName: varchar('first_name', { length: 100 }).notNull(),
lastName: varchar('last_name', { length: 100 }).notNull(),
avatarUrl: varchar('avatar_url', { length: 500 }).optional(),
bio: text('bio').optional(),
website: varchar('website', { length: 255 }).optional(),
location: json('location').optional(), // Store as JSON
preferences: json('preferences').default({}), // User preferences
isActive: boolean('is_active').default(true),
isVerified: boolean('is_verified').default(false),
lastLoginAt: timestamp('last_login_at').optional(),
createdAt: timestamp('created_at', { mode: 'default' }).defaultNow(),
updatedAt: timestamp('updated_at', { mode: 'default' }).defaultNow()
});
// Enhanced products table
export const products = pgTable('products', {
id: serial('id').primaryKey(),
name: varchar('name', { length: 255 }).notNull(),
slug: varchar('slug', { length: 255 }).unique().notNull(),
description: text('description').notNull(),
shortDescription: varchar('short_description', { length: 500 }).optional(),
price: real('price').notNull(),
compareAtPrice: real('compare_at_price').optional(),
sku: varchar('sku', { length: 100 }).unique().notNull(),
barcode: varchar('barcode', { length: 50 }).unique().optional(),
trackInventory: boolean('track_inventory').default(true),
stockQuantity: integer('stock_quantity').default(0),
minStockLevel: integer('min_stock_level').default(0),
weight: real('weight').optional(),
dimensions: json('dimensions').optional(), // { length, width, height }
images: json('images').default([]), // Array of image URLs
isDigital: boolean('is_digital').default(false),
isActive: boolean('is_active').default(true),
featured: boolean('featured').default(false),
rating: real('rating').default(0),
reviewCount: integer('review_count').default(0),
createdAt: timestamp('created_at', { mode: 'default' }).defaultNow(),
updatedAt: timestamp('updated_at', { mode: 'default' }).defaultNow()
});
// Orders table
export const orders = pgTable('orders', {
id: serial('id').primaryKey(),
orderNumber: varchar('order_number', { length: 50 }).unique().notNull(),
customerId: integer('customer_id').references(() => users.id).notNull(),
status: paymentStatusEnum('status').default('pending'),
subtotal: real('subtotal').notNull(),
tax: real('tax').notNull(),
shipping: real('shipping').notNull(),
total: real('total').notNull(),
currency: varchar('currency', { length: 3 }).default('USD'),
shippingAddress: json('shipping_address').notNull(),
billingAddress: json('billing_address').notNull(),
notes: text('notes').optional(),
shippedAt: timestamp('shipped_at').optional(),
deliveredAt: timestamp('delivered_at').optional(),
createdAt: timestamp('created_at', { mode: 'default' }).defaultNow(),
updatedAt: timestamp('updated_at', { mode: 'default' }).defaultNow()
});
// Order items table
export const orderItems = pgTable('order_items', {
id: serial('id').primaryKey(),
orderId: integer('order_id').references(() => orders.id).notNull(),
productId: integer('product_id').references(() => products.id).notNull(),
quantity: integer('quantity').positive().notNull(),
unitPrice: real('unit_price').notNull(),
totalPrice: real('total_price').notNull(),
productName: varchar('product_name', { length: 255 }).notNull(), // Store product name at time of order
productSku: varchar('product_sku', { length: 100 }).notNull(),
createdAt: timestamp('created_at', { mode: 'default' }).defaultNow()
});
// Reviews table
export const reviews = pgTable('reviews', {
id: serial('id').primaryKey(),
productId: integer('product_id').references(() => products.id).notNull(),
customerId: integer('customer_id').references(() => users.id).notNull(),
rating: integer('rating').min(1).max(5).notNull(),
title: varchar('title', { length: 255 }).optional(),
content: text('content').notNull(),
isVerified: boolean('is_verified').default(false),
isApproved: boolean('is_approved').default(true),
helpfulCount: integer('helpful_count').default(0),
createdAt: timestamp('created_at', { mode: 'default' }).defaultNow(),
updatedAt: timestamp('updated_at', { mode: 'default' }).defaultNow()
});
// Notifications table
export const notifications = pgTable('notifications', {
id: serial('id').primaryKey(),
userId: integer('user_id').references(() => users.id).notNull(),
type: notificationTypeEnum('type').notNull(),
title: varchar('title', { length: 255 }).notNull(),
message: text('message').notNull(),
data: json('data').optional(),
isRead: boolean('is_read').default(false),
createdAt: timestamp('created_at', { mode: 'default' }).defaultNow(),
readAt: timestamp('read_at').optional()
});
// 2. Enhanced Relations
export const usersRelations = relations(users, ({ many }) => ({
orders: many(orders, { relationName: 'customer' }),
reviews: many(reviews, { relationName: 'customer' }),
notifications: many(notifications, { relationName: 'user' })
}));
export const productsRelations = relations(products, ({ many }) => ({
orderItems: many(orderItems, { relationName: 'product' }),
reviews: many(reviews, { relationName: 'product' })
}));
export const ordersRelations = relations(orders, ({ one, many }) => ({
customer: one(users, {
fields: [orders.customerId],
references: [users.id],
relationName: 'orders'
}),
items: many(orderItems, { relationName: 'order' })
}));
export const orderItemsRelations = relations(orderItems, ({ one }) => ({
order: one(orders, {
fields: [orderItems.orderId],
references: [orders.id],
relationName: 'items'
}),
product: one(products, {
fields: [orderItems.productId],
references: [products.id],
relationName: 'items'
})
}));
export const reviewsRelations = relations(reviews, ({ one }) => ({
product: one(products, {
fields: [reviews.productId],
references: [products.id],
relationName: 'reviews'
}),
customer: one(users, {
fields: [reviews.customerId],
references: [users.id],
relationName: 'reviews'
})
}));
export const notificationsRelations = relations(notifications, ({ one }) => ({
user: one(users, {
fields: [notifications.userId],
references: [users.id],
relationName: 'notifications'
})
}));
// 3. Migration Functions
// Migration configuration
export const migrationConfig = {
migrationsFolder: './migrations',
database: DATABASE_URL
};
// Create initial migration
export async function createInitialMigration() {
const migrationId = Date.now().toString();
const migrationSql = `
-- Create users table
CREATE TABLE IF NOT EXISTS users (
id SERIAL PRIMARY KEY,
username VARCHAR(50) UNIQUE NOT NULL,
email VARCHAR(255) UNIQUE NOT NULL,
password_hash VARCHAR(255) NOT NULL,
first_name VARCHAR(100) NOT NULL,
last_name VARCHAR(100) NOT NULL,
avatar_url VARCHAR(500),
bio TEXT,
website VARCHAR(255),
location JSONB,
preferences JSONB DEFAULT '{}',
is_active BOOLEAN DEFAULT TRUE,
is_verified BOOLEAN DEFAULT FALSE,
last_login_at TIMESTAMP,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Create indexes
CREATE INDEX IF NOT EXISTS idx_users_email ON users(email);
CREATE INDEX IF NOT EXISTS idx_users_username ON users(username);
CREATE INDEX IF NOT EXISTS idx_users_active ON users(is_active);
-- Create products table
CREATE TABLE IF NOT EXISTS products (
id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL,
slug VARCHAR(255) UNIQUE NOT NULL,
description TEXT NOT NULL,
short_description VARCHAR(500),
price REAL NOT NULL,
compare_at_price REAL,
sku VARCHAR(100) UNIQUE NOT NULL,
barcode VARCHAR(50) UNIQUE,
track_inventory BOOLEAN DEFAULT TRUE,
stock_quantity INTEGER DEFAULT 0,
min_stock_level INTEGER DEFAULT 0,
weight REAL,
dimensions JSONB,
images JSONB DEFAULT '[]',
is_digital BOOLEAN DEFAULT FALSE,
is_active BOOLEAN DEFAULT TRUE,
featured BOOLEAN DEFAULT FALSE,
rating REAL DEFAULT 0,
review_count INTEGER DEFAULT 0,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Create products indexes
CREATE INDEX IF NOT EXISTS idx_products_sku ON products(sku);
CREATE INDEX IF NOT EXISTS idx_products_active ON products(is_active);
CREATE INDEX IF NOT EXISTS idx_products_featured ON products(featured);
CREATE INDEX IF NOT EXISTS idx_products_rating ON products(rating);
-- Create orders table
CREATE TABLE IF NOT EXISTS orders (
id SERIAL PRIMARY KEY,
order_number VARCHAR(50) UNIQUE NOT NULL,
customer_id INTEGER REFERENCES users(id) NOT NULL,
status VARCHAR(20) DEFAULT 'pending' CHECK (status IN ('pending', 'processing', 'completed', 'failed', 'refunded')),
subtotal REAL NOT NULL,
tax REAL NOT NULL,
shipping REAL NOT NULL,
total REAL NOT NULL,
currency VARCHAR(3) DEFAULT 'USD',
shipping_address JSONB NOT NULL,
billing_address JSONB NOT NULL,
notes TEXT,
shipped_at TIMESTAMP,
delivered_at TIMESTAMP,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Create orders indexes
CREATE INDEX IF NOT EXISTS idx_orders_customer ON orders(customer_id);
CREATE INDEX IF NOT EXISTS idx_orders_status ON orders(status);
CREATE INDEX IF NOT EXISTS idx_orders_created_at ON orders(created_at);
-- Add foreign key constraint
ALTER TABLE orders ADD CONSTRAINT fk_orders_customer
FOREIGN KEY (customer_id) REFERENCES users(id) ON DELETE CASCADE;
-- Insert migration record
INSERT INTO drizzle_migrations (id, name, created_at)
VALUES ('${migrationId}', 'initial_schema', CURRENT_TIMESTAMP);
`;
return migrationSql;
}
// 4. Advanced Query Examples with Relations
// Get user with all their orders and reviews
export async function getUserWithOrdersAndReviews(userId: number) {
return db.query.users
.findFirst({
where: eq(users.id, userId),
with: {
orders: {
orderBy: desc(orders.createdAt),
with: {
items: {
with: {
product: true
}
}
}
},
reviews: {
where: eq(reviews.isApproved, true),
orderBy: [desc(reviews.createdAt), desc(reviews.id)],
limit: 10
}
}
});
}
// Get product with reviews and order statistics
export async function getProductWithStats(productId: number) {
const product = await db.query.products
.findFirst({
where: eq(products.id, productId),
with: {
reviews: {
where: eq(reviews.isApproved, true),
orderBy: desc(reviews.createdAt),
limit: 10
},
orderItems: {
with: {
order: {
columns: {
id: true,
orderNumber: true,
customerId: true,
status: true
}
}
}
}
}
});
if (!product) return null;
// Calculate order statistics
const [orderStats] = await db
.select({
totalOrders: count(orderItems.id),
totalQuantity: sum(orderItems.quantity),
totalRevenue: sum(orderItems.totalPrice)
})
.from(orderItems)
.where(eq(orderItems.productId, productId));
return {
...product,
orderStats: orderStats || {
totalOrders: 0,
totalQuantity: 0,
totalRevenue: 0
}
};
}
// Get orders with customer and product information
export async function getOrdersWithDetails() {
return db.query.orders
.findMany({
with: {
customer: true,
items: {
with: {
product: {
columns: {
id: true,
name: true,
sku: true,
price: true
}
}
}
}
},
orderBy: desc(orders.createdAt)
});
}
// Get customer order history with statistics
export async function getCustomerOrderHistory(customerId: number) {
const orders = await db.query.orders
.findMany({
where: eq(orders.customerId, customerId),
with: {
items: {
with: {
product: true
}
}
},
orderBy: desc(orders.createdAt)
});
// Calculate statistics
const [stats] = await db
.select({
totalOrders: count(orders.id),
totalSpent: sum(orders.total),
orderCount: count(orders.id)
})
.from(orders)
.where(eq(orders.customerId, customerId));
return {
orders,
stats: stats || { totalOrders: 0, totalSpent: 0, orderCount: 0 }
};
}
// 5. Complex Business Logic Operations
// Complete order processing
export async function processOrder(orderData: {
customerId: number;
items: Array<{ productId: number; quantity: number }>;
shippingAddress: any;
billingAddress: any;
}) {
return db.transaction(async (tx) => {
// Calculate totals
let subtotal = 0;
let items = [];
for (const item of orderData.items) {
// Check product availability
const [product] = await tx
.select()
.from(products)
.where(eq(products.id, item.productId))
.limit(1);
if (!product) {
throw new Error(`Product ${item.productId} not found`);
}
if (product.trackInventory && product.stockQuantity < item.quantity) {
throw new Error(`Insufficient stock for product ${product.name}. Available: ${product.stockQuantity}, Requested: ${item.quantity}`);
}
const unitPrice = product.price;
const totalPrice = unitPrice * item.quantity;
subtotal += totalPrice;
// Store item details
items.push({
productId: product.id,
productName: product.name,
productSku: product.sku,
quantity: item.quantity,
unitPrice,
totalPrice
});
// Update inventory if tracking
if (product.trackInventory) {
await tx
.update(products)
.set({
stockQuantity: sql`${products.stockQuantity} - ${item.quantity}`,
updatedAt: new Date()
})
.where(eq(products.id, item.productId));
}
}
// Calculate tax and shipping (simplified)
const tax = subtotal * 0.1; // 10% tax
const shipping = items.length > 0 ? 5.99 : 0; // Flat shipping rate
const total = subtotal + tax + shipping;
// Generate order number
const orderNumber = `ORD-${Date.now()}`;
// Create order
const [order] = await tx
.insert(orders)
.values({
orderNumber,
customerId: orderData.customerId,
status: 'pending',
subtotal,
tax,
shipping,
total,
shippingAddress: orderData.shippingAddress,
billingAddress: orderData.billingAddress,
currency: 'USD'
})
.returning();
// Create order items
await tx.insert(orderItems).values(
items.map(item => ({
orderId: order.id,
...item
}))
);
// Create notification for customer
await tx.insert(notifications).values({
userId: orderData.customerId,
type: 'success',
title: 'Order Placed Successfully',
message: `Your order ${orderNumber} has been placed successfully.`,
data: {
orderId: order.id,
orderNumber,
total
}
});
return {
order,
items
};
});
}
// Bulk operations
export async function bulkUpdateProductPrices(updates: Array<{ productId: number; newPrice: number }>) {
const result = await db.transaction(async (tx) => {
const updatedProducts = await Promise.all(
updates.map(async ({ productId, newPrice }) => {
const [product] = await tx
.update(products)
.set({ price: newPrice, updatedAt: new Date() })
.where(eq(products.id, productId))
.returning();
return product;
})
);
return updatedProducts;
});
return result;
}
// 6. Data Analysis and Reporting
// Sales report by date range
export async function getSalesReport(startDate: Date, endDate: Date) {
const sales = await db
.select({
date: sql`DATE(orders.created_at)`.as('date'),
orderCount: count(orders.id),
totalRevenue: sum(orders.total),
averageOrderValue: sql`AVG(orders.total)`
})
.from(orders)
.where(
and(
gte(orders.createdAt, startDate),
lte(orders.createdAt, endDate)
)
)
.groupBy(sql`DATE(orders.created_at)`)
.orderBy(sql`DATE(orders.created_at)`);
return sales;
}
// Top performing products
export async function getTopPerformingProducts(limit: number = 10) {
return db
.select({
id: products.id,
name: products.name,
sku: products.sku,
price: products.price,
totalSold: sum(orderItems.quantity),
totalRevenue: sum(orderItems.totalPrice),
orderCount: count(sql`DISTINCT ${orderItems.orderId}`),
averageRating: sql`COALESCECE(
(
SELECT AVG(rating)
FROM reviews
WHERE reviews.productId = products.id
AND reviews.isApproved = true
), 0
)`,
reviewCount: count(
sql`${reviews.id}`
)
})
.from(products)
.leftJoin(orderItems, eq(orderItems.productId, products.id))
.leftJoin(reviews, eq(reviews.productId, products.id))
.groupBy(products.id)
.orderBy(desc(sql`sum(${orderItems.totalPrice)`))
.limit(limit);
}
// Customer analytics
export async function getCustomerAnalytics() {
const analytics = await db
.select({
totalCustomers: count(users.id),
activeCustomers: count(sql`CASE WHEN ${users.lastLoginAt} > CURRENT_DATE - INTERVAL '30 days' THEN 1 END`),
verifiedCustomers: count(sql`${users.isVerified}`),
registrationTrend: sql`COUNT(CASE WHEN created_at >= CURRENT_DATE - INTERVAL '30 days' THEN 1 END)`,
topCountries: sql`json_agg(DISTINCT CASE
WHEN ${users.location} IS NOT NULL
THEN (${users.location}->>'country')
ELSE 'Unknown'
END) ORDER BY count(*) DESC LIMIT 5)`
})
.from(users);
// Recent registrations
const recentRegistrations = await db
.select()
.from(users)
.orderBy(desc(users.createdAt))
.limit(10);
return {
analytics: analytics[0],
recentRegistrations
};
}
// 7. Backup and Data Export
// Export user data for GDPR compliance
export async function exportUserData(userId: number) {
const userData = await db.query.users
.findFirst({
where: eq(users.id, userId),
with: {
orders: {
columns: {
id: true,
orderNumber: true,
status: true,
total: true,
createdAt: true
},
with: {
items: {
columns: {
productName: true,
quantity: true,
unitPrice: true,
totalPrice: true
}
}
}
},
reviews: {
columns: {
id: true,
rating: true,
title: true,
content: true,
createdAt: true
}
},
notifications: {
columns: {
id: true,
type: true,
title: true,
message: true,
isRead: true,
createdAt: true
}
}
}
});
return userData;
}
// Inventory report
export async function getInventoryReport() {
return db
.select({
id: products.id,
name: products.name,
sku: products.sku,
price: products.price,
stockQuantity: products.stockQuantity,
minStockLevel: products.minStockLevel,
trackInventory: products.trackInventory,
reorderPoint: sql`${products.minStockLevel}`,
stockStatus: sql`
CASE
WHEN ${products.trackInventory} = false THEN 'Not tracked'
WHEN ${products.stockQuantity} <= ${products.minStockLevel} THEN 'Low stock'
WHEN ${products.stockQuantity} = 0 THEN 'Out of stock'
ELSE 'In stock'
END
`,
lastUpdated: products.updatedAt,
totalValue: sql`${products.price} * ${products.stockQuantity}`
})
.from(products)
.orderBy(
sql`CASE
WHEN ${products.stockQuantity} <= ${products.minStockLevel} THEN 1
ELSE 2
END`,
asc(sql`${products.name}`)
.where(eq(products.isActive, true));
}
// 8. Search and Filtering
// Advanced product search with filters
export function searchProducts(filters: {
query?: string;
minPrice?: number;
maxPrice?: number;
category?: string[];
minRating?: number;
inStock?: boolean;
sortBy?: 'name' | 'price' | 'rating' | 'created_at';
sortOrder?: 'asc' | 'desc';
limit?: number;
page?: number;
}) {
let query = db.select().from(products).where(eq(products.isActive, true));
// Add text search
if (filters.query) {
query = query.where(
or(
ilike(products.name, `%${filters.query}%`),
ilike(products.description, `%${filters.query}%`),
ilike(products.shortDescription, `%${filters.query}%`)
)
);
}
// Add price range filter
if (filters.minPrice !== undefined) {
query = query.where(gte(products.price, filters.minPrice));
}
if (filters.maxPrice !== undefined) {
query = query.where(lte(products.price, filters.maxPrice));
}
// Add stock filter
if (filters.inStock !== undefined) {
if (filters.inStock) {
query = query.where(gt(products.stockQuantity, 0));
} else {
query = query.where(eq(products.stockQuantity, 0));
}
}
// Add rating filter
if (filters.minRating !== undefined) {
query = query.where(gte(products.rating, filters.minRating));
}
// Add sorting
const sortBy = filters.sortBy || 'created_at';
const sortOrder = filters.sortOrder || 'desc';
query = query.orderBy(
sortOrder === 'asc' ? asc(sql`${sortBy}`) : desc(sql`${sortBy}`)
);
// Add pagination
if (filters.limit) {
query = query.limit(filters.limit);
if (filters.page && filters.page > 1) {
query = query.offset((filters.page - 1) * filters.limit);
}
}
return query;
}
// 9. Type Definitions
import type { PgTable } from 'drizzle-orm/pg-core';
import { InferSelectModel } from 'drizzle-orm';
export type User = InferSelectModel<typeof users>;
export type Product = InferSelectModel<typeof products>;
export type Order = InferSelectModel<typeof orders>;
export type OrderItem = InferSelectModel<typeof orderItems>;
export <type T> New = Insertable<PgTable<T>>;
export {
users,
products,
orders,
orderItems,
reviews,
notifications,
db,
migrationConfig,
getUserWithOrdersAndReviews,
getProductWithStats,
getOrdersWithDetails,
getCustomerOrderHistory,
processOrder,
bulkUpdateProductPrices,
getSalesReport,
getTopPerformingProducts,
getCustomerAnalytics,
exportUserData,
getInventoryReport,
searchProducts
};