◆ DB 接続時にスキーマの列情報をすべて取得して保持
  ◆ Node.js の場合リクエストごとにしなくていいのでパフォーマンス的には気にしなくて大丈夫
◆ 追加した insert/update メソッドでは 保持した列情報を参照して存在しない列は除外
  ◆ 色々考慮し始めると Knex を本体を変更しないとダメかも

Knex の問題点

Knex で insert や update メソッドにオブジェクトを渡すと全部のプロパティが INSERT や UPDATE の対象となります
それはテーブルに存在しない列も含まれます
そうなると実行時に存在しない列を指定したことによるエラーが起きます

INSERT や UPDATE したい列のプロパティのみのオブジェクトを渡せばよいのですが 実際にはその他の情報を色々含んでいることがあり このためだけにオブジェクトをフィルタする必要があります
これが結構面倒で 列が多いテーブルだと列をすべて列挙しないといけないです
さらにテーブル定義を更新したら そのフィルタ処理まで変更しないといけません
これが忘れやすくて 消した列が SQL には残っていてエラーになったり 追加した列が SQL に含まれず更新されなかったりします

INSERT や UPDATE を行うテーブルはわかっているのでそこに存在する列以外は無視してほしいです
これを実現するために データベースとは別に プログラム側でもテーブルに対応したクラスを用意している作りのものも見かけます
それは結局 2 重管理してることになりますし 更新の手間や漏れが出るのは変わりません
プログラム側のみの管理で SQL ファイルは一切使わない方法もなくはないですが 静的なものなら SQL の方が見やすいですし 同じデータベースを使う異なるアプリケーションがある場合に それらすべてにテーブル情報を保持したくないです
言語が同じならそこだけモジュール化して使い回せますが 異なる場合も十分考えられます

対処する

プログラム側にコードとして持たせておくとメンテの手間が発生しますが 起動時に動的に取得すればその問題はなくなります
Node.js の場合は プロセスを停止しない限り変数を共有できるので 起動時にデータベースから取得しておけば リクエスト処理のたびに取得しなくて済みます

こういうモジュールを用意しました

[knex-init.js]
const Knex = require("knex")

module.exports = async knex => {
const { rows } = await knex.raw("SELECT table_name, column_name FROM information_schema.columns WHERE table_schema = current_schema()")

const tables = {}

for (const {table_name, column_name} of rows) {
if (tables[table_name]) tables[table_name].add(column_name)
else tables[table_name] = new Set([column_name])
}

const filter = (table, row) => {
const cols = tables[table]
if (!cols) return row
const filtered = {}
for (const [col, value] of Object.entries(row)) {
if (cols.has(col)) {
filtered[col] = value
}
}
return filtered
}

Knex.QueryBuilder.extend("smartInsert", function (data, ...others) {
const table = this._single.table
const filtered = Array.isArray(data)
? data.map(filter.bind(null, table))
: filter(table, data)
return this.insert(filtered, ...others)
})

Knex.QueryBuilder.extend("smartUpdate", function (data, ...others) {
if (!(data && typeof data === "object")) {
return this.update(data, ...others)
}
const table = this._single.table
return this.update(filter(table, data), ...others)
})
}

使い方はこういう感じです
user テーブルは id と name の列のみが存在します

const knex = require("knex")(...)
const init = require("./knex-init.js")

!async function main() {
await init(knex)

console.log(knex("user").insert({ id: 10, name: "user1", foo: "bar" }).toString())
console.log(knex("user").where("id", 10).update({ name: "user2", foo: "bar" }).toString())

console.log(knex("user").smartInsert({ id: 10, name: "user1", foo: "bar" }).toString())
console.log(knex("user").where("id", 10).smartUpdate({ name: "user2", foo: "bar" }).toString())
}()
insert into "user" ("foo", "id", "name") values ('bar', 10, 'user1')
update "user" set "name" = 'user2', "foo" = 'bar' where "id" = 10
insert into "user" ("id", "name") values (10, 'user1')
update "user" set "name" = 'user2' where "id" = 10

上 2 つは通常の insert/update を使っていて 下 2 つは今回用意した smartInsert/smartUpdate を使っています
上側は存在しない foo 列の追加や更新を行っていますが 下側は foo の追加や更新は行っていません

やってること

knex インスタンスを受け取って そのインスタンスの connection の current_schema の列情報をすべて取得します
テーブルごとの列リストを保持しておきます

smartInsert と smartUpdate メソッドを追加して 呼び出されたときに現在のコンテキストで指定されているテーブルに存在する列のみにフィルタしてから insert や update メソッドを呼び出しています

制限

とりあえず作ってみたものなので 不便なところや考慮してないところもあります

まず列情報は最初に取得して固定です
実行中にテーブル定義を変えても自動で更新はされません
Node.js プロセスを再起動する必要があります
テーブル定義を更新するなら普通はソースコードの変更もありますし問題はないと思います

smartInsert と smartUpdate は knex グローバルです
インスタンスを渡していますが QueryBuilder の extend を使うのですべてのインスタンスに影響します
複数の knex インスタンスがあって接続先が異なると すべて最後に init したインスタンスの接続先の情報を使用してしまいます
this を元にして 参照するテーブル・列情報を切り替えたりするのが良さそうです

smartInsert と smartUpdate は実行時のテーブル情報を元にします
SQL の書き順どおりに テーブル情報を指定する前に insert や update から書こうとすると動きません
また 後から table メソッドなどで変更した場合も対応していません
これをやろうとすると QueryBuilder ではなく QueryCompiler の _prepInsert や _prepUpdate を変更しないとダメそうです

_single.table は knex("ここ") に入れたものそのままです
スキーマ情報や as も含まれます (「schema.table as t」など)
今回の例はそういうのを考慮していません
列を含むかチェックするためにテーブル名だけを取り出さないといけないです

最後に 列情報の取得処理は PostgreSQL を前提にしています
MySQL や SQLite などでも列情報を取得することはできると思いますが client の種類によってここの処理をかえないといけないです