Guide TypeORM migration workflow for Lety 2.0 Backend — generate, review, run, revert and register migrations across tenant/platform/auth schemas. Triggered when the user needs to create or manage a database migration.
You are guiding a TypeORM migration for the Lety 2.0 Backend. The project has three separate databases, each with its own migration config and scripts.
Priority rule: Always follow TypeORM official documentation. Never write raw DDL SQL manually — always use
migration:generatefrom entity changes. If existing migrations contain raw SQL that could have been generated, note it.
libs/common/src/migrations/tenant/)# Generate from entity changes
pnpm migration:generate:tenant <MigrationName>
# Run pending
pnpm migration:run:tenant
# Revert last
pnpm migration:revert:tenant
libs/common/src/migrations/platform/)pnpm migration:generate:platform <MigrationName>
pnpm migration:run:platform
pnpm migration:revert:platform
libs/common/src/migrations/auth/)pnpm migration:generate:auth <MigrationName>
pnpm migration:run:auth
pnpm migration:revert:auth
Determine what the user needs:
Ask: which database? (tenant | platform | auth) — required to pick the right script.
Before running migration:generate, verify with the user:
TypeOrmModule.forFeature([...]) in its modulepnpm migration:generate:<database> <DescriptiveName>
Naming rules for <DescriptiveName>:
AddInvoiceTable, AddStatusColumnToAgents, RemoveDeprecatedFieldMigration1, Fix, Update — too vagueWhat TypeORM generates:
libs/common/src/migrations/<database>/<timestamp>-<descriptiveName>.ts<DescriptiveName><timestamp> implements MigrationInterfaceup(): SQL to apply the changedown(): SQL to reverse the changeAfter generating, read the file and verify:
up() review:
default: option)down() review:
up() in reverse orderup() adds column X, down() drops column Xup() creates index, down() drops itCommon generation issues to flag:
| Issue | What to tell the user |
|---|---|
down() is empty or missing | "Migration is irreversible — add manual down() if rollback is needed" |
| Column dropped unexpectedly | "Verify entity still has the field — TypeORM may have detected a rename as drop+add" |
| Data type change (e.g. int → decimal) | "This may truncate data — run on a test DB first and verify data migration is needed" |
| Multiple unrelated changes in one migration | "Consider splitting into separate migrations for clarity and safer rollback" |
synchronize detected in config | "Remove synchronize: true from production config — use migrations exclusively" |
After generating, the migration must be registered in the migrations index:
Tenant: libs/common/src/migrations/tenant/index.ts
Platform: libs/common/src/migrations/platform/index.ts
Auth: libs/common/src/migrations/auth/index.ts
Pattern:
import { ExistingMigration1234 } from './1234-existingMigration';
import { NewMigration5678 } from './5678-newMigration'; // add this
export const TENANT_MIGRATIONS = [
ExistingMigration1234,
NewMigration5678, // add this — order matters, keep chronological
];
TypeORM runs migrations in array order. Always append at the end — never reorder existing entries.
pnpm migration:run:<database>
Before running in production:
down() works: run → revert → run againAfter running:
migrations table marking the migration as appliedSELECT * FROM migrations ORDER BY timestamp DESC LIMIT 5;pnpm migration:revert:<database>
down() method of that migrationrevert multiple timesUse manual migrations only when migration:generate cannot express the change:
When writing manually, use QueryRunner methods — never raw string SQL:
// PREFERRED — QueryRunner API
await queryRunner.addColumn('table_name', new TableColumn({
name: 'new_column',
type: 'varchar',
isNullable: true,
}));
await queryRunner.createIndex('table_name', new TableIndex({
name: 'IDX_table_field',
columnNames: ['field'],
}));
Only use queryRunner.query() for:
await queryRunner.query('UPDATE table SET col = value WHERE condition')CREATE EXTENSION)Always implement a correct down() that reverses every operation.
CREATE TABLE, ALTER TABLE, DROP TABLE as raw SQL for schema migrations — use TypeORM CLIsynchronize: true is only for seed/dev modules — never in productiondown() reverses up() exactlyMigration123 or Fix