Database Migrations
Yew Search uses TypeORM migrations to manage database schema changes in a version-controlled, reversible way.
Overview
Migrations allow you to:
- Track schema changes in version control (Git)
- Apply changes incrementally in development and production
- Rollback changes if something goes wrong
- Collaborate safely with multiple developers
- Avoid data loss from schema synchronization
IMPORTANT: Never use synchronize: true in production. It can drop tables and cause data loss.
Quick Start
Check Migration Status
See which migrations have run and which are pending:
npm run migration:show
Run Pending Migrations
Apply all pending migrations to your database:
npm run migration:run
Rollback Last Migration
Revert the most recently applied migration:
npm run migration:revert
Creating Migrations
Method 1: Auto-Generate from Entity Changes (Recommended)
TypeORM can automatically generate migrations by comparing your entities to the database schema.
Workflow:
- Modify an entity file (e.g., add a column)
- Generate migration from changes:
npm run migration:generate -- src/migrations/AddEmailVerification - Review the generated migration file
- Test on development database:
npm run migration:run - Commit migration to Git
Example:
# After adding a new field to UserEntity
npm run migration:generate -- src/migrations/add-user-preferences
# TypeORM generates: src/migrations/1234567890-add-user-preferences.ts
# Review, test, commit
Method 2: Create Empty Migration for Manual SQL
For complex changes or raw SQL, create an empty migration template:
npm run migration:create -- src/migrations/add-custom-indexes
Then fill in the up() and down() methods manually.
Migration File Structure
Every migration has two methods:
import { MigrationInterface, QueryRunner } from 'typeorm';
export class AddEmailVerification1234567890 implements MigrationInterface {
// up() - Apply the change
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`
ALTER TABLE "user"
ADD COLUMN email_verified boolean DEFAULT false;
`);
}
// down() - Revert the change
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`
ALTER TABLE "user"
DROP COLUMN email_verified;
`);
}
}
Key Points:
up(): Applies your schema change (runs when executing migration)down(): Reverts your schema change (runs when rolling back)- Transactions: TypeORM wraps migrations in transactions automatically
- Query Runner: Provides database-agnostic API for schema changes
Configuration
Migrations are configured in two places:
1. DataSource Configuration (backend/src/config/typeorm.config.ts)
This file is used by the TypeORM CLI:
export default new DataSource({
type: 'postgres',
// ... database connection settings
entities: [/* all entities */],
migrations: ['src/migrations/*{.ts,.js}'],
synchronize: false, // NEVER true with migrations!
});
2. NestJS Module (backend/src/app.module.ts)
The runtime configuration for your application:
TypeOrmModule.forRootAsync({
useFactory: (configService: ConfigService) => ({
// ... database settings
synchronize: configService.get('NODE_ENV') === 'development',
autoLoadEntities: true,
}),
})
IMPORTANT: Set synchronize: false in production to prevent automatic schema changes.
Available Scripts
All migration scripts are defined in backend/package.json:
| Script | Description |
|---|---|
npm run migration:show | Show migration status (which have run) |
npm run migration:run | Run all pending migrations |
npm run migration:revert | Rollback the last executed migration |
npm run migration:generate -- src/migrations/name | Auto-generate migration from entity changes |
npm run migration:create -- src/migrations/name | Create empty migration template |
Best Practices
1. Always Review Auto-Generated Migrations
TypeORM's auto-generation is helpful but not perfect. Always review generated SQL before running:
npm run migration:generate -- src/migrations/update-user
# Open src/migrations/*-update-user.ts and review the SQL
2. Test Both Up and Down
Always test that migrations can be rolled back:
npm run migration:run # Apply migration
npm run migration:revert # Test rollback
npm run migration:run # Re-apply
3. Use Descriptive Names
Good migration names describe the change:
- ✅
add-user-email-verification - ✅
fix-timestamps-to-timestamptz - ✅
create-integration-indexes - ❌
update1 - ❌
migration - ❌
fix
4. One Logical Change Per Migration
Keep migrations focused on a single logical change:
# Good - Separate migrations
npm run migration:create -- src/migrations/add-user-preferences
npm run migration:create -- src/migrations/add-integration-retry
# Bad - Combined (harder to revert selectively)
npm run migration:create -- src/migrations/multiple-changes
5. Never Edit Existing Migrations
Once a migration has been committed and run in any environment (dev, staging, production), never edit it.
Instead:
- Create a new migration to fix the issue
- Or revert the old migration and create a new one
6. Commit Migrations to Git
Migrations are code. Always commit them:
git add src/migrations/
git add src/**/entities/*.entity.ts # If you changed entities
git commit -m "Add migration for user preferences"
7. Backup Before Production Migrations
Always backup your production database before running migrations:
# PostgreSQL backup
pg_dump -U postgres -d yew > backup_$(date +%Y%m%d_%H%M%S).sql
# Run migration
npm run migration:run
# If needed, restore
psql -U postgres -d yew < backup_20260118_120000.sql
8. Use Safe SQL Patterns
Write SQL that's safe to run multiple times when possible:
-- ❌ WRONG - Fails if column exists
ALTER TABLE user ADD COLUMN email varchar(255);
-- ✅ CORRECT - Safe to run multiple times
ALTER TABLE user ADD COLUMN IF NOT EXISTS email varchar(255);
9. Document Complex Migrations
Add comments to explain non-obvious changes:
export class FixTimestamps1234567890 implements MigrationInterface {
/**
* Converting timestamp to timestamptz requires specifying the timezone
* for existing data. We assume all timestamps are UTC (from JavaScript Date objects).
*/
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`
ALTER TABLE "user"
ALTER COLUMN created_at TYPE timestamptz USING created_at AT TIME ZONE 'UTC';
`);
}
}
10. Check Migration Status Regularly
Make it a habit to check migration status:
# Before starting work
npm run migration:show
# After pulling changes
npm run migration:run
Common Workflows
Adding a New Column
-
Add column to entity:
@Column({ type: 'boolean', default: false })
public emailVerified: boolean; -
Generate migration:
npm run migration:generate -- src/migrations/add-email-verified -
Review and test:
npm run migration:run
Renaming a Column
TypeORM can't auto-detect renames, so create manually:
npm run migration:create -- src/migrations/rename-user-status
Then write the SQL:
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`
ALTER TABLE "user"
RENAME COLUMN status TO account_status;
`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`
ALTER TABLE "user"
RENAME COLUMN account_status TO status;
`);
}
Adding an Index
npm run migration:create -- src/migrations/add-user-email-index
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`
CREATE INDEX IF NOT EXISTS idx_user_email ON "user"(email);
`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`
DROP INDEX IF EXISTS idx_user_email;
`);
}
Deployment
Development Environment
# Pull latest code
git pull
# Run any new migrations
npm run migration:run
# Start dev server
npm run start:dev
Production Environment
# Backup database first!
pg_dump -U postgres -d yew_production > backup_$(date +%Y%m%d_%H%M%S).sql
# Pull latest code
git pull
# Install dependencies (if needed)
npm install
# Run migrations
npm run migration:run
# Restart application
pm2 restart yew-backend
Troubleshooting
Migration Failed - How to Recover
If a migration fails halfway through:
# 1. Check what went wrong
npm run migration:show
# 2. Try to revert (if possible)
npm run migration:revert
# 3. Or manually fix database and update migrations table
psql -U postgres -d yew
SELECT * FROM migrations ORDER BY timestamp DESC;
DELETE FROM migrations WHERE timestamp = 1234567890; -- Remove failed entry
# 4. Fix the migration SQL and try again
npm run migration:run
Migration Already Applied
If you see "migration already applied" error:
# Check status
npm run migration:show
# If it shows as applied but didn't actually run, manually remove from migrations table
psql -U postgres -d yew
DELETE FROM migrations WHERE name = 'MigrationName1234567890';
Entity and Database Out of Sync
If your entities don't match the database:
# Generate migration to fix differences
npm run migration:generate -- src/migrations/sync-schema
# Review carefully before running!
cat src/migrations/*-sync-schema.ts
# Apply if correct
npm run migration:run
Migration History
Initial Migration (2026-01-18)
fix-all-timestamps-to-timestamptz
- Converted all timestamp columns to timestamptz across 5 tables
- Fixed timezone inconsistency causing polling issues
- See:
src/migrations/1737225600000-fix-all-timestamps-to-timestamptz.ts
Resources
- TypeORM Migrations Documentation
- PostgreSQL ALTER TABLE Documentation
- Backend Plans: Migration System Research
- Backend Plans: Polling Issues and Fixes
Support
If you encounter issues with migrations:
- Check the Troubleshooting section above
- Review the migration file comments for context
- Check
backend/plans/migrations.mdfor detailed research - Open an issue on GitHub with migration logs