--- title: Use Database Migrations impact: HIGH impactDescription: Enables safe, repeatable database schema changes tags: database, migrations, typeorm, schema --- ## Use Database Migrations Never use `synchronize: true` in production. Use migrations for all schema changes. Migrations provide version control for your database, enable safe rollbacks, and ensure consistency across all environments. **Incorrect (using synchronize or manual SQL):** ```typescript // Use synchronize in production TypeOrmModule.forRoot({ type: 'postgres', synchronize: true, // DANGEROUS in production! // Can drop columns, tables, or data }); // Manual SQL in production @Injectable() export class DatabaseService { async addColumn(): Promise { await this.dataSource.query('ALTER TABLE users ADD COLUMN age INT'); // No version control, no rollback, inconsistent across envs } } // Modify entities without migration @Entity() export class User { @Column() email: string; @Column() // Added without migration newField: string; // Will crash in production if synchronize is false } ``` **Correct (use migrations for all schema changes):** ```typescript // Configure TypeORM for migrations // data-source.ts export const dataSource = new DataSource({ type: 'postgres', host: process.env.DB_HOST, port: parseInt(process.env.DB_PORT), username: process.env.DB_USERNAME, password: process.env.DB_PASSWORD, database: process.env.DB_NAME, entities: ['dist/**/*.entity.js'], migrations: ['dist/migrations/*.js'], synchronize: false, // Always false in production migrationsRun: true, // Run migrations on startup }); // app.module.ts TypeOrmModule.forRootAsync({ inject: [ConfigService], useFactory: (config: ConfigService) => ({ type: 'postgres', host: config.get('DB_HOST'), synchronize: config.get('NODE_ENV') === 'development', // Only in dev migrations: ['dist/migrations/*.js'], migrationsRun: true, }), }); // migrations/1705312800000-AddUserAge.ts import { MigrationInterface, QueryRunner } from 'typeorm'; export class AddUserAge1705312800000 implements MigrationInterface { name = 'AddUserAge1705312800000'; public async up(queryRunner: QueryRunner): Promise { // Add column with default to handle existing rows await queryRunner.query(` ALTER TABLE "users" ADD "age" integer DEFAULT 0 `); // Add index for frequently queried columns await queryRunner.query(` CREATE INDEX "IDX_users_age" ON "users" ("age") `); } public async down(queryRunner: QueryRunner): Promise { // Always implement down for rollback await queryRunner.query(`DROP INDEX "IDX_users_age"`); await queryRunner.query(`ALTER TABLE "users" DROP COLUMN "age"`); } } // Safe column rename (two-step) export class RenameNameToFullName1705312900000 implements MigrationInterface { public async up(queryRunner: QueryRunner): Promise { // Step 1: Add new column await queryRunner.query(` ALTER TABLE "users" ADD "full_name" varchar(255) `); // Step 2: Copy data await queryRunner.query(` UPDATE "users" SET "full_name" = "name" `); // Step 3: Add NOT NULL constraint await queryRunner.query(` ALTER TABLE "users" ALTER COLUMN "full_name" SET NOT NULL `); // Step 4: Drop old column (after verifying app works) await queryRunner.query(` ALTER TABLE "users" DROP COLUMN "name" `); } public async down(queryRunner: QueryRunner): Promise { await queryRunner.query(`ALTER TABLE "users" ADD "name" varchar(255)`); await queryRunner.query(`UPDATE "users" SET "name" = "full_name"`); await queryRunner.query(`ALTER TABLE "users" DROP COLUMN "full_name"`); } } ``` Reference: [TypeORM Migrations](https://typeorm.io/migrations)