Bookshelf.js のモダンな代替:Sutando への移行
Bookshelf.js はかつて、Knex.js の上にシンプルで Promise ベースのインターフェースを求める Node.js 開発者の定番 ORM でした。しかし 2022 年以降アップデートがなく、TypeScript…
Bookshelf.js はかつて、Knex.js の上にシンプルで Promise ベースのインターフェースを求める Node.js 開発者の定番 ORM でした。しかし 2022 年以降アップデートがなく、TypeScript サポートもなく、コミュニティも移行してしまいました。モダンな代替を検討する時が来ています。このガイドでは、Bookshelf.js から Sutando への移行手順を解説します。
Bookshelf.js の現状
Bookshelf.js は 2022 年 6 月のバージョン 1.2.0 以降、新リリースがありません。事実上放置されていることを示す複数の兆候があります:
- 長年リリースなし:npm パッケージが 2022 年以降更新されていない
- TypeScript サポートなし:ライブラリは純粋な CommonJS JavaScript で、組み込み型がない——
@types/bookshelfの別パッケージが必要 - コミュニティのコンセンサス:npm-compare、SaaSHub、多数のブログが Bookshelf を「レガシー」または「メンテナンスモード」と表現
- StackOverflow の質問:開発者が「Bookshelf.js の積極的にメンテナンスされている代替はありますか?」と活発に質問
なぜ Sutando なのか?
Sutando は Laravel の Eloquent にインスピレーションを受けた、モダンな Node.js 向け Active Record ORM です。Bookshelf.js との比較:
| 機能 | Bookshelf.js | Sutando |
|---|---|---|
| パターン | Active Record(Collection ベース) | Active Record(Model ベース) |
| TypeScript | なし(@types/bookshelf が必要) |
ファーストクラス TS サポート |
| モジュールシステム | CommonJS (require) |
ESM + CommonJS |
| マイグレーション | Knex 経由(別ツール) | 組み込み schema builder |
| 最終リリース | 2022 | 活発 |
| リレーション | forge().fetch() |
hasMany(), belongsTo() |
| ソフトデリート | 手動 | 組み込み |
| フック | initialize イベント |
creating, saved 等 |
| スコープ | 手動 | 組み込み |
概念マッピング:Bookshelf.js → Sutando
初期化
Bookshelf.js:
import knex from 'knex';
import bookshelf from 'bookshelf';
const knexInstance = knex({
client: 'pg',
connection: process.env.DATABASE_URL,
});
const db = bookshelf(knexInstance);
db.plugin('registry');
db.plugin('virtuals');
db.plugin('pagination');
Sutando:
import { sutando } from 'sutando';
sutando.addConnection({
client: 'pg',
connection: process.env.DATABASE_URL,
});
export default sutando;
プラグイン不要——virtuals、pagination、registry のような機能は組み込み済みです。
モデル
Bookshelf.js:
const User = db.model('User', {
tableName: 'users',
posts() {
return this.hasMany('Post');
},
profile() {
return this.belongsTo('Profile');
},
});
// または extend を使用
const User = db.Model.extend({
tableName: 'users',
posts() {
return this.hasMany('Post');
},
});
Sutando:
import { Model } from 'sutando';
class User extends Model {
protected table = 'users';
relationPosts() {
return this.hasMany(Post);
}
relationProfile() {
return this.belongsTo(Profile);
}
}
クエリ
Bookshelf.js:
// 全件取得
const users = await new User().fetch();
// リレーション付き取得
const user = await new User({ id: 1 }).fetch({
withRelated: ['posts.comments'],
});
// 条件クエリ
const users = await User.where('age', '>', 18).fetch();
// 挿入
const user = await new User({ name: 'Alice', email: 'alice@example.com' }).save();
// 更新
const user = await new User({ id: 1 }).fetch();
user.set('name', 'Bob');
await user.save();
// 削除
const user = await new User({ id: 1 }).fetch();
await user.destroy();
Sutando:
// 全件取得
const users = await User.query().get();
// リレーション付き取得
const user = await User.query().with('posts.comments').find(1);
// 条件クエリ
const users = await User.query().where('age', '>', 18).get();
// 挿入
const user = new User({ name: 'Alice', email: 'alice@example.com' });
await user.save();
// 更新
const user = await User.query().find(1);
user.name = 'Bob';
await user.save();
// 削除
const user = await User.query().find(1);
await user.delete();
ページネーション
Bookshelf.js はページネーションプラグインが必要:
db.plugin('pagination');
const results = await new User().fetchPage({
page: 1,
pageSize: 20,
});
// results.models, results.pagination
Sutando はページネーションが組み込み:
const results = await User.query().paginate(1, 20);
// results.items(), results.total(), results.currentPage(), results.perPage()
トランザクション
Bookshelf.js:
await db.transaction(async (t) => {
const user = await new User({ name: 'Alice' }).save(null, { transacting: t });
await new Post({ title: 'Hello', user_id: user.id }).save(null, { transacting: t });
});
Sutando:
const db = sutando.connection();
await db.transaction(async (trx) => {
const user = new User({ name: 'Alice' });
await user.save({ client: trx });
const post = new Post({ title: 'Hello', user_id: user.id });
await post.save({ client: trx });
});
フック / イベント
Bookshelf.js:
const User = db.Model.extend({
tableName: 'users',
initialize() {
this.on('saving', this.onSaving);
},
onSaving(model, attrs, options) {
model.set('updated_at', new Date());
},
});
Sutando:
class User extends Model {
protected table = 'users';
static booted() {
this.saving(model => {
model.updated_at = new Date();
});
}
}
ステップバイステップ移行ガイド
ステップ 1:Sutando のインストール
npm install sutando
ステップ 2:データベース接続の置き換え
Bookshelf の初期化を Sutando に置き換え:
import { sutando } from 'sutando';
sutando.addConnection({
client: 'pg',
connection: process.env.DATABASE_URL,
});
export default sutando;
bookshelf と @types/bookshelf パッケージを削除:
npm uninstall bookshelf @types/bookshelf
ステップ 3:モデルを extend から ES6 クラスに変換
Bookshelf の Model.extend() パターンは ES6 クラス構文に変換:
tableName→protected tablehasMany('Post')→this.hasMany(Post)(文字列名ではなくクラス参照を使用)belongsTo('Profile')→this.belongsTo(Profile)initialize()とイベントリスナー →static booted()内でthis.saving()、this.creating()を登録
ステップ 4:クエリパターンの更新
クエリパターンの主な変更点:
new User().fetch()→User.query().get()new User({ id: 1 }).fetch({ withRelated: [...] })→User.query().with('...').find(1)model.set('key', value)→model.key = value(直接プロパティ代入)model.destroy()→model.delete().fetchPage()→.paginate()
ステップ 5:マイグレーションの移行
Knex マイグレーションを使用している場合、Sutando の schema builder と互換性があります:
const { Migration } = require('sutando');
module.exports = class extends Migration {
async up(schema) {
await schema.createTable('users', (table) => {
table.increments('id');
table.string('name');
table.string('email').unique();
table.timestamps();
});
}
async down(schema) {
await schema.dropTableIfExists('users');
}
};
移行後に得られるもの
- ファーストクラス TypeScript:
@types/bookshelfは不要——型が組み込みで正確 - ESM サポート:
import/exportをネイティブに使用、require()は不要 - プラグイン不要:ページネーション、virtuals、registry は組み込み機能
- 直接プロパティアクセス:
user.set('name', 'Bob')ではなくuser.name = 'Bob' - 組み込みソフトデリート:
SoftDeletesプラグイン(compose(Model, SoftDeletes))を追加するだけ——手動実装不要 - 組み込みスコープ:カスタムプラグインなしで再利用可能なクエリフィルターを定義
- Eloquent スタイル API:Laravel を使ったことがある人に馴染みやすい
- 活発なメンテナンス:定期的なアップデートと新機能
結論
Bookshelf.js はその時代において優れた ORM でしたが、2022 年以降のアップデート停止と TypeScript サポートの欠如により、モダンな Node.js プロジェクトではリスクとなります。Sutando は自然なアップグレードパスを提供します——同じ Active Record パターンでありながら、ファーストクラスの TypeScript サポート、Bookshelf でプラグインが必要だった機能の組み込み、そして活発にメンテナンスされたコードベースを備えています。
移行はシンプルです:モデルを extend() から ES6 クラスに変換し、クエリパターンを更新し、Bookshelf の依存を削除します。両方の ORM は移行期間中に共存できるため、モデルごとに段階的に移行できます。
👉 Sutando を始める または npm install sutando を実行して試してみましょう。