コンテンツにスキップ
Sutando
Esc
↑↓移動↵開く⌘Jプレビュー

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 table
  • hasMany('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 を実行して試してみましょう。

このページは役に立ちましたか?