之前曾在 twitter 上看到有人在稱讚 knex 是個不錯的 query builder。這次剛好在寫 side project 時便想來用用看。
初見的推文們
- https://twitter.com/WanCW/status/1604435977539162112
- https://twitter.com/thecat/status/1004932162120974337
好處
- 和 model 脫鉤
- 減低對資料庫的相依性
- 若要在程式碼中要管理一堆 SQL 語句字串,並不是很好維護。
- 能幫助開發者做到有系統地 migration
壞處
- 多一層自然會需要多耗點資源
How to knex
tl;dr
yarn add knex # 安裝 knex
yarn add better-sqlite3 # 也記得要安裝對應的 db driver
knex init # 建立 config 檔
knex migrate:make task_name # 產生可改的 migration 檔,且自動加上 timestamp 前綴
knex migrate:latest # migrate 到最新的 db scheme (按時序執行全部 migration file 的 up function)
knex migrate:rollback --all # rollback 到最初的 db scheme
knex seed:make # 產生可改的 seed 檔
knex seed:run # 執行 seeding
knex init
knex init
會在該專案的 package.json
旁產生 knexfile.js
。
我們可以在裡面指定 migration 和 seed 的目錄,也可以指定在 make 時要用的模板 stub 檔。
module.exports = {
// ...
development: {
client: {/* ... */},
connection: {/* ... */},
migrations: {
directory: './knex/migrations/',
stub: './knex/migration.stub'
},
seeds: {
directory: './knex/seeds/',
stub: './knex/seed.stub'
}
}
}
提供的目錄路徑是相對於 knexfile.json
。
migration
migration 的用途是「更改 table 的結構,並留下記錄」。
所以每個 migration 都會有 up & down 這兩個 export,down 通常是為了出意外時可以 rollback。
這邊記得要 return,不能只是在裡面呼叫而已。(為了 de 這個 bug 花了我不少時間⋯)
/**
* @param { import("knex").Knex } knex
* @returns { Promise<void> }
*/
exports.up = function(knex) {
return knex.schema.createTable("users", (table) => {
table.increments()
table.string('account')
table.unique('account')
table.string('salted_password')
})
}
/**
* @param { import("knex").Knex } knex
* @returns { Promise<void> }
*/
exports.down = function(knex) {
return knex.schema.dropTable("users")
}
seed
seed 的用途可以理解為建立假資料或基礎資料。有點不確定為什麼要獨立於 migration,雖說 schema 和 data 分開放感覺挺合理,但不知道是不是有什麼特殊理由。
順帶一提,這邊就不用寫 return
了。
/**
* @param { import("knex").Knex } knex
* @returns { Promise<void> }
*/
exports.seed = async function(knex) {
// Deletes ALL existing entries
await knex('users').del()
await knex('users').insert([
{ account: "user001", salted_password: "blablabla" }
]);
};
再補充一下, knex migration:make
所產生的檔案會有 timestamp 前綴,下 knex migration:latest
指令跑全部 migration 時會按 timestamp 順序執行;相對地,knex seed:run
則是按字母順序。不知道這是不是把 migration 和 seed 分開的原因之一。
minor tip: with yarn
因為不想在全域安裝 knex,所以在 package.json 裡面的 scripts 區塊多一個指令:
// package.json
{
"scripts": {
"knex": "knex"
},
}
這樣就可以在專案目錄下跑 yarn knex
來執行 knex 的 cli 指令了。
使用參考
- https://twitter.com/lingjieowl/status/1517081490768547841
- https://ithelp.ithome.com.tw/articles/10239291