Skip to main content

Database Overview

The Ever Works template uses Drizzle ORM with PostgreSQL as its database layer. The database is optional -- the application can run without it for content-only deployments -- but it powers all user, subscription, engagement, and admin features.

Technology Stack

ComponentTechnologyPurpose
ORMDrizzle ORMType-safe query builder and schema management
DatabasePostgreSQLPrimary relational database
Driverpostgres (postgres.js)PostgreSQL client for Node.js
MigrationsDrizzle KitSchema migration generation and execution
Seedingdrizzle-seed + custom scriptsDatabase initialization with default data

Database Architecture

Configuration

Drizzle Config (drizzle.config.ts)

export default {
schema: "./lib/db/schema.ts",
out: "./lib/db/migrations",
dialect: "postgresql",
dbCredentials: {
url: process.env.DATABASE_URL,
},
} satisfies Config;

The configuration points to:

  • Schema file: lib/db/schema.ts -- the single source of truth for all table definitions
  • Migrations output: lib/db/migrations/ -- where generated SQL migration files are stored
  • Dialect: PostgreSQL
  • Connection: Via DATABASE_URL environment variable

Connection Management (lib/db/drizzle.ts)

The database connection is lazily initialized on first use and reuses connections across hot reloads in development via a global singleton pattern.

Key features:

  • Lazy initialization: The database connection is not created until the first query is executed
  • Proxy-based access: The exported db object uses a JavaScript Proxy to initialize the connection transparently
  • Connection pooling: Configurable pool size via DB_POOL_SIZE environment variable (default: 20 in production, 10 in development, clamped 1-50)
  • Idle timeout: Connections are released after 20 seconds of inactivity
  • Connect timeout: 30-second timeout for establishing new connections
  • Singleton pattern: Uses globalThis to persist connections across Next.js hot reloads
// Usage - just import and use
import { db } from '@/lib/db/drizzle';

const users = await db.select().from(schema.users);

Environment Variables

VariableRequiredDefaultDescription
DATABASE_URLNo-PostgreSQL connection string
DB_POOL_SIZENo10/20Connection pool size (dev/prod)

When DATABASE_URL is not set, database features are silently disabled, allowing the application to run in content-only mode.

Schema Overview

The database schema is defined in a single file (lib/db/schema.ts) containing 40+ tables organized by domain:

DomainTablesDescription
Users & Auth8Users, accounts, sessions, tokens, authenticators
Roles & Permissions3RBAC with roles, permissions, and role-permission mappings
Client Profiles1Extended user profiles for client accounts
Content Engagement4Comments, votes, favorites, item views
Subscriptions4Plans, subscription history, payment providers, payment accounts
Notifications1In-app notification system
Admin & Moderation4Reports, moderation history, item audit logs, activity logs
Integrations2CRM config, integration mappings
Companies2Companies and item-company associations
Sponsor Ads1Sponsored item advertisements
Surveys2Surveys and survey responses
Newsletter1Newsletter subscriptions
System1Seed status tracking

Database Initialization

On application startup (via instrumentation.ts), the template automatically:

  1. Runs migrations: Drizzle's migrate() function applies any pending migrations (idempotent -- already-applied migrations are skipped)
  2. Seeds data: If the database has not been seeded, the seed script runs with advisory lock protection to prevent race conditions in multi-process deployments

This is handled by lib/db/initialize.ts. See the Migrations Guide and Database Seeding for details.

Key Commands

# Generate a migration from schema changes
pnpm db:generate

# Run pending migrations
pnpm db:migrate

# Seed the database
pnpm db:seed

# Open Drizzle Studio (database GUI)
pnpm db:studio