Wildsky F.

Easy things should be easy, and hard things should be possible.



初見 Knex.js

Posted on

之前曾在 twitter 上看到有人在稱讚 knex 是個不錯的 query builder。這次剛好在寫 side project 時便想來用用看。

初見的推文們

好處

  • 和 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 指令了。

使用參考


Ref