import { pgTable, text, timestamp, integer, boolean, foreignKey } from 'drizzle-orm/pg-core';
import { drizzle } from 'drizzle-orm/node-postgres';
import { eq, like, desc, sql, and } from 'drizzle-orm';
import { Pool } from 'pg';
import { migrate } from 'drizzle-orm/node-postgres/migrator';
// ============================================
// Schema Definition
// ============================================
export const users = pgTable('users', {
id: integer('id').primaryKey().generatedAlwaysAsIdentity(),
email: text('email').unique().notNull(),
name: text('name').notNull(),
passwordHash: text('password_hash').notNull(),
role: text('role', { enum: ['user', 'admin'] }).default('user'),
createdAt: timestamp('created_at').defaultNow(),
updatedAt: timestamp('updated_at').defaultNow(),
});
export const posts = pgTable('posts', {
id: integer('id').primaryKey().generatedAlwaysAsIdentity(),
authorId: integer('author_id').references(() => users.id, { onDelete: 'cascade' }),
title: text('title').notNull(),
content: text('content'),
published: boolean('published').default(false),
viewCount: integer('view_count').default(0),
createdAt: timestamp('created_at').defaultNow(),
updatedAt: timestamp('updated_at').defaultNow(),
});
export const comments = pgTable('comments', {
id: integer('id').primaryKey().generatedAlwaysAsIdentity(),
postId: integer('post_id').references(() => posts.id, { onDelete: 'cascade' }),
authorId: integer('author_id').references(() => users.id, { onDelete: 'set null' }),
content: text('content').notNull(),
createdAt: timestamp('created_at').defaultNow(),
});
// ============================================
// Database Connection
// ============================================
const pool = new Pool({
connectionString: process.env.DATABASE_URL || 'postgres://postgres:password@localhost:5432/myapp',
});
// Create Drizzle instance
export const db = drizzle(pool, {
schema: { users, posts, comments },
});
// ============================================
// Types (Inferred)
// ============================================
export type User = typeof users.$inferSelect;
export type NewUser = typeof users.$inferInsert;
export type Post = typeof posts.$inferSelect;
export type NewPost = typeof posts.$inferInsert;
// ============================================
// Repository Pattern Example
// ============================================
export class UserRepository {
static async getAll() {
return db.select().from(users).orderBy(desc(users.createdAt));
}
static async getById(id: number) {
return db.query.users.findFirst({
where: eq(users.id, id),
with: {
posts: {
limit: 5,
orderBy: desc(posts.createdAt),
},
},
});
}
static async getByEmail(email: string) {
return db.query.users.findFirst({
where: eq(users.email, email),
});
}
static async create(data: NewUser) {
const result = await db.insert(users).values(data).returning();
return result[0];
}
static async update(id: number, data: Partial<NewUser>) {
const result = await db
.update(users)
.set({ ...data, updatedAt: new Date() })
.where(eq(users.id, id))
.returning();
return result[0];
}
static async delete(id: number) {
return db.delete(users).where(eq(users.id, id)).returning();
}
}
export class PostRepository {
static async getFeed(limit = 20, offset = 0) {
return db.query.posts.findMany({
where: eq(posts.published, true),
limit,
offset,
orderBy: desc(posts.createdAt),
with: {
author: {
columns: {
id: true,
name: true,
email: true,
},
},
comments: {
limit: 3,
with: {
author: {
columns: { name: true },
},
},
},
},
});
}
static async search(term: string) {
return db
.select()
.from(posts)
.where(
and(
eq(posts.published, true),
like(posts.title, `%${term}%`)
)
);
}
static async create(data: NewPost) {
return db.insert(posts).values(data).returning();
}
static async incrementViewCount(id: number) {
return db
.update(posts)
.set({ viewCount: sql`${posts.viewCount} + 1` })
.where(eq(posts.id, id))
.returning();
}
}
// ============================================
// Migration Runner
// ============================================
export async function runMigrations() {
console.log('Running migrations...');
try {
await migrate(db, { migrationsFolder: './drizzle' });
console.log('Migrations completed successfully');
} catch (error) {
console.error('Migration failed:', error);
process.exit(1);
}
}
// ============================================
// Seeding Helper
// ============================================
export async function seed() {
console.log('Seeding database...');
// Clear existing data
await db.delete(comments);
await db.delete(posts);
await db.delete(users);
// Create users
const [user1] = await db.insert(users).values({
email: 'alice@example.com',
name: 'Alice',
passwordHash: 'hashed_password',
role: 'admin',
}).returning();
const [user2] = await db.insert(users).values({
email: 'bob@example.com',
name: 'Bob',
passwordHash: 'hashed_password',
role: 'user',
}).returning();
// Create posts
await db.insert(posts).values([
{
title: 'Hello Drizzle',
content: 'Drizzle ORM is awesome!',
authorId: user1.id,
published: true,
},
{
title: 'Draft Post',
content: 'Work in progress...',
authorId: user1.id,
published: false,
},
{
title: 'Bob\'s Thoughts',
content: 'TypeScript is great.',
authorId: user2.id,
published: true,
},
]);
console.log('Seeding completed');
}
// ============================================
// Usage Example
// ============================================
/*
import { UserRepository, PostRepository } from './db';
async function main() {
const users = await UserRepository.getAll();
const feed = await PostRepository.getFeed();
console.log({ users, feed });
}
*/