Files
lcbp3/.agents/skills/nestjs-best-practices/rules/db-use-migrations.md
admin ef16817f38
All checks were successful
Build and Deploy / deploy (push) Successful in 4m44s
260223:1415 20260223 nextJS & nestJS Best pratices
2026-02-23 14:15:06 +07:00

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)