Database
Complete guide to working with PostgreSQL, Drizzle ORM, and UUID-based data architecture in your SaaS application.Table of Contents
- UUID-First Architecture
- Database Stack
- Schema Organization
- Common Operations
- Development Workflow
- Quick Reference
UUID-First Architecture
Critical Setup Required: This application uses UUID primary keys throughout the database schema. You must enable the UUID extension before running any migrations:
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";Security & Privacy:
- No sequential IDs that reveal business metrics
- Difficult to guess or enumerate resources
- Better for public-facing APIs
- Works across distributed databases
- No ID conflicts in multi-server environments
- Easier database merging and replication
- Consistent ID format across all tables
- Type-safe with TypeScript
- No auto-increment race conditions
Database Stack
Modern, type-safe database infrastructure designed for scalability:Core Technologies
Database Features:
- PostgreSQL 15+ with full ACID compliance
- UUID extension for primary key generation
- JSON/JSONB support for flexible data
- Full-text search capabilities
- Advanced indexing strategies
CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; -- UUID generation
CREATE EXTENSION IF NOT EXISTS "pg_trgm"; -- Text similarity
CREATE EXTENSION IF NOT EXISTS "btree_gin"; -- Advanced indexingProject Structure
src/
├── db/
│ ├── models/ # UUID-based schema definitions
│ │ ├── auth-model.ts # Users, sessions, accounts
│ │ ├── user-model.ts # User profiles, settings
│ │ ├── organization-model.ts # Organizations, memberships
│ │ ├── project-model.ts # Projects, tasks
│ │ ├── post-model.ts # Blog posts, translations
│ │ └── subscription-model.ts # Stripe subscriptions
│ ├── repositories/ # Type-safe data access layer
│ │ ├── user-repository.ts
│ │ ├── organization-repository.ts
│ │ ├── project-repository.ts
│ │ └── ...
│ ├── index.ts # Database connection & schema export
│ └── migrate.ts # Migration runner
├── drizzle.config.ts # Drizzle configuration
└── drizzle/ # Generated migrations
└── migrations/
├── 0001_*.sql
└── meta/
Schema Organization
All tables use UUID primary keys with logical grouping by domain:// src/db/models/auth-model.ts
export const users = pgTable('users', {
id: text('id').primaryKey().$defaultFn(() => crypto.randomUUID()),
email: varchar('email', { length: 255 }).unique().notNull(),
name: varchar('name', { length: 255 }),
image: text('image'),
emailVerified: boolean('email_verified').default(false),
createdAt: timestamp('created_at').defaultNow().notNull(),
})
export const sessions = pgTable('sessions', {
id: text('id').primaryKey().$defaultFn(() => crypto.randomUUID()),
userId: text('user_id').references(() => users.id).notNull(),
expiresAt: timestamp('expires_at').notNull(),
token: text('token').unique().notNull(),
})
export const accounts = pgTable('accounts', {
id: text('id').primaryKey().$defaultFn(() => crypto.randomUUID()),
userId: text('user_id').references(() => users.id).notNull(),
provider: varchar('provider', { length: 50 }).notNull(),
providerAccountId: varchar('provider_account_id', { length: 255 }).notNull(),
})Common Operations
Essential database operations for daily development and production management:Initial Setup:Daily Development:Development Workflow:
# Enable UUID extension (run once per database)
psql $DATABASE_URL -c "CREATE EXTENSION IF NOT EXISTS \"uuid-ossp\";"
# Install dependencies and setup
pnpm install
pnpm db:push # Push schema to development database
pnpm db:seed # Add demo data with UUIDs# Open database management interface
pnpm db:studio # Launch Drizzle Studio at localhost:4983
# Test database connection
pnpm db:check # Verify connectivity and permissions
# Quick development reset
pnpm db:reset-seed # Drop all data and reseed (development only)# 1. Make schema changes in src/db/models/
# 2. Push changes to dev database
pnpm db:push
# 3. Test changes in Drizzle Studio
pnpm db:studio
# 4. Generate production migration when ready
pnpm db:generateDevelopment Workflow
Step-by-step process for working with the UUID-based database:1. Plan Your Changes:2. Implement Repository Functions:3. Test with Development Data:
// Example: Adding user preferences
export const userPreferences = pgTable('user_preferences', {
id: text('id').primaryKey().$defaultFn(() => crypto.randomUUID()),
userId: text('user_id').references(() => users.id).notNull(),
theme: themeEnum('theme').default('system'),
language: varchar('language', { length: 10 }).default('en'),
timezone: varchar('timezone', { length: 50 }).default('UTC'),
createdAt: timestamp('created_at').defaultNow().notNull(),
})// src/db/repositories/user-repository.ts
export async function getUserPreferences(userId: string) {
return await db.query.userPreferences.findFirst({
where: eq(userPreferences.userId, userId),
})
}
export async function updateUserPreferences(
userId: string,
prefs: Partial<InferInsertModel<typeof userPreferences>>
) {
return await db
.update(userPreferences)
.set({ ...prefs, updatedAt: new Date() })
.where(eq(userPreferences.userId, userId))
.returning()
}# Push schema changes
pnpm db:push
# Open Drizzle Studio to verify
pnpm db:studio
# Test with real UUID data
pnpm db:seedQuick Reference
Common patterns and operations for UUID-based database work:// User operations with UUIDs
export async function getUserById(userId: string) {
return await db.query.users.findFirst({
where: eq(users.id, userId),
with: {
organizations: { with: { members: true } },
settings: true,
},
})
}
export async function createUser(userData: CreateUserType) {
const [newUser] = await db.insert(users)
.values({
...userData,
id: crypto.randomUUID(), // Explicit UUID generation
})
.returning()
return newUser
}
// Organization operations
export async function getOrganizationWithMembers(orgId: string) {
return await db.query.organizations.findFirst({
where: eq(organizations.id, orgId),
with: {
members: { with: { user: true } },
projects: true,
},
})
}
export async function addMemberToOrganization(userId: string, orgId: string, role: MemberRole) {
return await db.insert(members).values({
id: crypto.randomUUID(),
userId,
organizationId: orgId,
role,
}).returning()
}Next Steps
Explore the complete database documentation:Learning Path: Start with Repository Pattern → Drizzle Queries → Migration Workflow → Production Best Practices
Detailed Guides
- Repository Pattern - UUID-based data access patterns and repository creation
- Drizzle Queries - Advanced querying, relationships, and performance optimization
- Migration Workflow - Schema evolution and production deployment
- Drizzle Studio - Database management GUI and debugging tools
- Best Practices - Performance tuning, security, and troubleshooting
Quick Access
# Essential commands for daily work
pnpm db:studio # Open database GUI
pnpm db:push # Apply schema changes (dev)
pnpm db:seed # Load demo data with UUIDs
pnpm db:generate # Create production migrationsUUID Benefits: Enhanced security, better scalability, and consistent developer experience across the entire application stack.