Day 19 - 專案升級必備:資料庫 Migration 實戰
前言
在專案開發時,我們常常會遇到「資料庫 Schema 需要修改」的情況。
例如新增欄位、刪除欄位、或是調整欄位型別。
最直覺的方式是 —— 直接改 Entity,然後靠 TypeORM 的 synchronize: true
來自動同步。
但是,這樣做真的安全嗎? 🤔
⚠️ synchronize: true
的隱藏風險
在 DataSource
設定中,大家應該都看過這段:
1 | synchronize: true, // 開發階段方便,正式環境建議 false |
這個參數會讓 TypeORM 在每次啟動專案時,自動比對 Entity 與資料庫結構,並「直接修改資料庫」。
好處:
- 開發初期很快:改
User.ts
或Todo.ts
,資料庫自動更新。 - 不需要寫 SQL,完全自動化。
壞處:
- 資料可能直接被刪掉:欄位名稱改了,TypeORM 可能直接 drop + recreate table。
- 多人協作很危險:大家本地 Entity 不一致 → DB schema 會亂掉。
- Production 大忌:上線環境如果還開
synchronize: true
,一個小改動就可能讓線上資料消失。
👉 這就是我們需要 Migration 的理由。
為什麼要用 Migration?
Migration 就像「資料庫的 Git」,能幫助我們做到:
- 版本控制:每次改動都被記錄下來。
- 可回滾:跑錯了可以 revert。
- 多人協作:大家跑同一份 Migration,確保一致性。
- 安全上線:Production 可以放心執行,避免不可預期的自動修改。
Migration 實戰:導入 Migration 的步驟
第 1 步:在 package.json
添加 Migration 指令
1 | "scripts": { |
第 2 步:建立 migrations
資料夾
1 | mkdir -p src/migrations |
第 3 步:修改 src/config/db.ts
將 synchronize
改為 false
,並加入 migration 設定:
1 | import "reflect-metadata"; |
第 4 步:生成初始 Migration
如果之前用 synchronize: true
已經有資料表,建議先清空,再重新用 migration 建立。
清空方式如下: (Dbeaver 選取 Table 右鍵點擊 Delete)
1 | npm run migration:generate src/migrations/InitialMigration |
這會生成一個 migration 檔案,內容類似這樣:
1 | import { MigrationInterface, QueryRunner } from "typeorm"; |
第 5 步:執行 Migration
執行前可以先查看一下狀態
1 | npm run migration:show # 查看狀態 |
可以發現有偵測到一個 migration 檔案尚未執行
1 | npm run migration:run # 執行 migration |
下圖為 migration 檔案執行結果 :
然後可以到資料庫查看目前狀況 :
可發現多了一張 migrations_history 表,這是用來記錄 migration 的資訊
第 6 步:其他常用指令
1 | # 回退最後一次 migration |
完整工作流程範例
首次設定
1
2
3mkdir -p src/migrations
npm run migration:generate src/migrations/InitialMigration
npm run migration:run未來修改 Entity 後 (這邊舉例在 User.ts Entity 加上一個 phone 欄位)
1
2
3
4
5
6
7
8
9
10
11
12@Entity()
export class User {
@PrimaryGeneratedColumn("uuid")
id!: string;
...
@Column({ type: "varchar", length: 10, nullable: true })
phone?: string;
...
}執行下方指令生成 migration 檔案
1
npm run migration:generate src/migrations/AddPhoneToUser
生成結果如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14import { MigrationInterface, QueryRunner } from "typeorm";
export class AddPhoneToUser1759482580830 implements MigrationInterface {
name = 'AddPhoneToUser1759482580830'
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "user" ADD "phone" character varying(10)`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "phone"`);
}
}執行下方指令執行 migration 檔案
1
npm run migration:run
可查看資料庫新增了 phone 欄位在最尾端
成功執行 migration 啦 🎉
如果需要回退
1
npm run migration:revert
Migration 開發流程
- 修改/新增 Entity
- 建立對應的 Migration
- 在本地測試
migration:run
- Commit Migration 檔案 → 其他人同步
- 部署時,CI/CD 自動跑 Migration
🔑 小提醒 & Best Practice
- 開發可以先用
synchronize: true
,但正式專案要關掉。 - 每個 Migration 檔案盡量只做「一件事」(新增欄位、刪表、調型別)。
- Migration 檔案要 一起 Commit,不然別人會少版本。
- 別忘了
migration:revert
可以救命 🙌。
結語
synchronize: true
很方便,但就像「測試環境的捷徑」,不能拿到 Production。
真正穩定的專案,必須靠 Migration 來管理資料庫版本。 🚀
參考資料
commit : disable synchronize and setup migration scripts