Files
Kordant/tasks/kordant-unified-restructure/10-db-connection-migrations.md
2026-05-25 22:49:37 -04:00

5.1 KiB

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:
    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:
    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:
    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.