260223:1415 20260223 nextJS & nestJS Best pratices
All checks were successful
Build and Deploy / deploy (push) Successful in 4m44s
All checks were successful
Build and Deploy / deploy (push) Successful in 4m44s
This commit is contained in:
129
.agents/skills/nestjs-best-practices/rules/db-use-migrations.md
Normal file
129
.agents/skills/nestjs-best-practices/rules/db-use-migrations.md
Normal file
@@ -0,0 +1,129 @@
|
||||
---
|
||||
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)
|
||||
Reference in New Issue
Block a user