TypeORM 迁移
DataSource 配置
// data-source.ts
import "reflect-metadata";
import { DataSource } from "typeorm";
export const AppDataSource = new DataSource({
type: "postgres",
url: process.env.DATABASE_URL,
synchronize: false, // NEVER true in production
logging: ["error", "migration"],
entities: ["src/entities/**/*.ts"],
migrations: ["src/migrations/**/*.ts"],
subscribers: ["src/subscribers/**/*.ts"],
});
// Initialize in app startup
await AppDataSource.initialize();
CLI 命令
在 package.json 中添加脚本:"typeorm": "ts-node -r tsconfig-paths/register ./node_modules/typeorm/cli.js --dataSource src/data-source.ts"
# Generate migration from entity changes
npm run typeorm migration:generate src/migrations/AddUserRole
# Create blank migration
npm run typeorm migration:create src/migrations/SeedInitialData
# Run pending migrations
npm run typeorm migration:run
# Revert last migration
npm run typeorm migration:revert
# Show migration status
npm run typeorm migration:show
# Sync schema WITHOUT migrations (dev only!)
npm run typeorm schema:sync
# Drop all tables (dangerous!)
npm run typeorm schema:drop
# Log SQL that would be generated
npm run typeorm schema:log
自定义迁移脚本
// src/migrations/1705312000000-AddUserRole.ts
import { MigrationInterface, QueryRunner, TableColumn } from "typeorm";
export class AddUserRole1705312000000 implements MigrationInterface {
name = "AddUserRole1705312000000";
async up(queryRunner: QueryRunner): Promise {
// Add column
await queryRunner.addColumn("users", new TableColumn({
name: "role",
type: "enum",
enum: ["admin", "user", "guest"],
default: "'user'"
}));
// Add index
await queryRunner.createIndex("users", {
name: "idx_users_role",
columnNames: ["role"]
} as any);
// Raw SQL (data migration)
await queryRunner.query(`
UPDATE users SET role = 'admin'
WHERE email LIKE '%@company.com'
`);
}
async down(queryRunner: QueryRunner): Promise {
await queryRunner.dropIndex("users", "idx_users_role");
await queryRunner.dropColumn("users", "role");
}
}
QueryRunner 方法
// Table operations
await queryRunner.createTable(new Table({ name: "...", columns: [...] }));
await queryRunner.dropTable("table_name");
await queryRunner.renameTable("old", "new");
// Column operations
await queryRunner.addColumn("users", new TableColumn({ ... }));
await queryRunner.dropColumn("users", "column_name");
await queryRunner.changeColumn("users", "old_col", new TableColumn({ ... }));
await queryRunner.renameColumn("users", "old", "new");
// Index & FK
await queryRunner.createIndex("users", new TableIndex({ ... }));
await queryRunner.dropIndex("users", "idx_name");
await queryRunner.createForeignKey("orders", new TableForeignKey({ ... }));
await queryRunner.dropForeignKey("orders", "fk_name");
// Raw SQL
await queryRunner.query("CREATE EXTENSION IF NOT EXISTS pgcrypto");
// Transaction control (migrations run in transactions by default)
await queryRunner.startTransaction();
await queryRunner.commitTransaction();
await queryRunner.rollbackTransaction();
程序化运行迁移
// Run migrations programmatically (e.g., on app startup)
import { AppDataSource } from "./data-source";
async function runMigrations() {
await AppDataSource.initialize();
const pending = await AppDataSource.showMigrations();
if (pending) {
console.log("Running pending migrations...");
await AppDataSource.runMigrations({ transaction: "all" });
console.log("Migrations complete");
}
}
// transaction option:
// "all" - wrap all migrations in one transaction
// "none" - no transaction
// "each" - separate transaction per migration