# 10. Database — PostgreSQL Connection, Migrations, and Seed Data meta: id: kordant-unified-restructure-10 feature: kordant-unified-restructure priority: P0 depends_on: [kordant-unified-restructure-09] tags: [backend, database, drizzle, postgres] objective: - Set up the database connection layer, migration tooling, and seed data for the unified monolith. Replace the current SQLite setup in `web/` with PostgreSQL, matching the production database used by the legacy `packages/db`. deliverables: - `web/src/server/db/index.ts` — Database connection module: - Creates PostgreSQL connection pool using `pg` or `@neondatabase/serverless` - Exports `db` instance initialized with Drizzle ORM - Handles connection string from environment variables - Graceful shutdown hook to close pool - `web/drizzle.config.ts` — Drizzle Kit configuration: - Points to `src/server/db/schema.ts` - Specifies PostgreSQL dialect - Reads database URL from env - `web/src/server/db/migrate.ts` — Migration runner script: - Programmatically runs pending migrations on startup - Can be called from `entry-server.tsx` or a standalone script - `web/src/server/db/seed.ts` — Seed script: - Creates sample users, subscriptions, watchlist items, alerts, blog posts - Idempotent: can be run multiple times without duplicates - Useful for development and demo environments - Environment configuration: - `DATABASE_URL` in `.env` and `.env.example` - Connection pooling settings (max connections, timeout) steps: 1. Install dependencies in `web/`: - `drizzle-orm`, `drizzle-kit` - `pg` (for local/dev) or `@neondatabase/serverless` (for serverless deploy) - `@types/pg` if using `pg` 2. Create `web/src/server/db/index.ts`: ```ts import { drizzle } from 'drizzle-orm/node-postgres'; import { Pool } from 'pg'; import * as schema from './schema'; const pool = new Pool({ connectionString: process.env.DATABASE_URL }); export const db = drizzle(pool, { schema }); ``` 3. Update `web/drizzle.config.ts`: ```ts import { defineConfig } from 'drizzle-kit'; export default defineConfig({ schema: './src/server/db/schema.ts', out: './drizzle', dialect: 'postgresql', dbCredentials: { url: process.env.DATABASE_URL! }, }); ``` 4. Create `web/src/server/db/migrate.ts`: - Import `migrate` from `drizzle-orm/node-postgres/migrator` - Run migrations from `./drizzle` folder - Log success or error 5. Create `web/src/server/db/seed.ts`: - Use `db.insert()` to populate tables - Create: 2-3 users, 1 family group, 2 subscriptions (basic + premium), 3-5 watchlist items, 2-3 exposures, 5-10 alerts, 3 blog posts, 2 property watchlist items, 1 removal request - Use deterministic IDs or check for existing data before inserting 6. Add `db:migrate`, `db:generate`, `db:push`, `db:seed` scripts to `web/package.json`. 7. Create `.env.example` in `web/` with `DATABASE_URL=postgresql://...` 8. Test locally: - Start PostgreSQL (Docker or local install) - Run `pnpm db:generate` to create migration SQL - Run `pnpm db:push` to apply schema - Run `pnpm db:seed` to populate data - Verify tables exist with correct data using `psql` or a GUI tool 9. Update `web/src/server/db/schema.ts` from task 09 if any adjustments are needed based on migration output. steps: - Integration: `pnpm db:generate` produces valid SQL - Integration: `pnpm db:push` creates all tables in local PostgreSQL - Integration: `pnpm db:seed` populates tables without errors - Integration: Application can query seeded data via `db.select()` acceptance_criteria: - [ ] `web/src/server/db/index.ts` exports a working Drizzle `db` instance - [ ] `drizzle.config.ts` is correctly configured for PostgreSQL - [ ] `pnpm db:generate` creates migration files in `web/drizzle/` - [ ] `pnpm db:push` applies schema to a PostgreSQL database successfully - [ ] `pnpm db:seed` populates all relevant tables with sample data - [ ] The app can perform `db.select().from(users)` and return seeded users - [ ] Environment variables are documented in `.env.example` validation: - `cd web && pnpm db:generate` — check `drizzle/` folder for SQL files - `cd web && pnpm db:push` — verify tables created via `psql -d kordant -c "\dt"` - `cd web && pnpm db:seed` — verify data exists via `psql -d kordant -c "SELECT COUNT(*) FROM users;"` - Create a temporary test route that queries `db.select().from(users)` and renders results notes: - The legacy project used Prisma + PostgreSQL in production. We are keeping PostgreSQL but switching to Drizzle ORM. - For local development, Docker Compose with PostgreSQL is recommended: ```yaml services: postgres: image: postgres:16-alpine environment: POSTGRES_USER: kordant POSTGRES_PASSWORD: kordant POSTGRES_DB: kordant ports: - "5432:5432" ``` - If deploying to Vercel/Netlify serverless, use `@neondatabase/serverless` instead of `pg`. - Migration files should be committed to git so all environments run the same migrations. - The seed script should be idempotent. Use `ON CONFLICT DO NOTHING` or check existence before inserting.