# Database Migrations The app uses [node-pg-migrate](https://github.com/salsita/node-pg-migrate) for versioned, reversible schema changes. ## How it works Boot sequence: 1. **Baseline init** — `src/db/database.js` runs `CREATE TABLE IF NOT EXISTS` and `ALTER TABLE ADD COLUMN IF NOT EXISTS` for the existing schema. This is the implicit baseline — everything that was in place before migrations were introduced. Idempotent on every boot. 2. **Migrations** — `src/db/migrate.js` runs every file in `/app/migrations/` that hasn't already been recorded in the `pgmigrations` table, in filename order. Each applied migration is inserted into `pgmigrations` so it only runs once. New schema changes should go in versioned migration files, not in the inline `database.js` init. ## Creating a migration ```bash docker exec -w /app pediatric-ai-scribe npm run migrate:new -- add_avatar_url ``` Creates a file like `migrations/1744601234567_add_avatar_url.js` with empty `up()` and `down()` functions. Edit it: ```js exports.up = (pgm) => { pgm.addColumn('users', { avatar_url: { type: 'text', notNull: false } }); pgm.createIndex('users', 'avatar_url'); }; exports.down = (pgm) => { pgm.dropIndex('users', 'avatar_url'); pgm.dropColumn('users', 'avatar_url'); }; ``` Full API: https://salsita.github.io/node-pg-migrate/ ## Running migrations Migrations apply automatically on app boot. To run them manually (e.g. before a restart): ```bash docker exec -w /app pediatric-ai-scribe npm run migrate:up ``` ## Rolling back Roll back the most recent migration: ```bash docker exec -w /app pediatric-ai-scribe npm run migrate:down ``` This calls the file's `down()`. If `down()` is empty or missing, the rollback is a no-op but the migration is removed from `pgmigrations` — meaning the next `up` will reapply it. ## Viewing state Which migrations have been applied: ```bash docker exec -w /app pediatric-ai-scribe npm run migrate:status ``` Or directly: ```bash docker exec pedscribe-db psql -U pedscribe -d pedscribe \ -c "SELECT id, name, run_on FROM pgmigrations ORDER BY id;" ``` ## Raw SQL migrations If pgm's JS helpers are limiting, drop to SQL: ```js exports.up = (pgm) => { pgm.sql(` CREATE INDEX CONCURRENTLY idx_audit_log_action ON audit_log (action) WHERE action IN ('login', 'login_failed', 'session_idle_timeout'); `); }; ``` Note: `CREATE INDEX CONCURRENTLY` cannot run inside a transaction. For that you need `exports.disableTransaction = true;` in the migration file. ## Conventions - One logical change per file. Don't bundle unrelated alters. - Always write `down()` unless rollback is fundamentally impossible (e.g., dropping a column that had unique data). - Name files by what the change does (`add_foo`, `backfill_bar`), not the ticket number. - Migrations run in filename order — the timestamp prefix ensures order across checkouts from different devs. - Never edit an already-applied migration. Write a new one to fix it. ## Relationship to the inline init `src/db/database.js` still runs on every boot and handles the pre-migration baseline. Do not add new schema changes there — use migrations. The inline init will gradually shrink as old CREATE TABLE statements age out.