130 lines
3.7 KiB
Markdown
130 lines
3.7 KiB
Markdown
---
|
|
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<void> {
|
|
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<void> {
|
|
// 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<void> {
|
|
// 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<void> {
|
|
// 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<void> {
|
|
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)
|