bookshelf、objection、sequelize 和 typeorm 都是 Node.js 生态中流行的 ORM(对象关系映射)库,用于简化数据库操作。sequelize 和 typeorm 功能全面,支持多种数据库,适合大型项目。objection 基于 SQL 查询构建器,灵活性高。bookshelf 较老,基于 Knex.js,但维护频率较低。这些工具帮助开发者用 JavaScript 对象操作数据库,避免直接编写 SQL。
在 Node.js 后端开发中,选择合适的 ORM(对象关系映射)框架直接影响项目的可维护性和开发效率。bookshelf、objection、sequelize 和 typeorm 是四个主流选择,但它们的设计理念和适用场景差异很大。本文将从实际工程角度,对比它们的核心特性。
bookshelf 目前处于维护模式,更新频率较低。
// bookshelf: 示例代码(注意:社区支持减少)
const Bookshelf = require('bookshelf')(knex);
const User = Bookshelf.Model.extend({ tableName: 'users' });
objection 维护良好,基于 Knex.js。
// objection: 示例代码
const { Model } = require('objection');
class User extends Model {
static get tableName() {
return 'users';
}
}
sequelize 稳定且功能丰富。
// sequelize: 示例代码
const { Sequelize, Model } = require('sequelize');
class User extends Model {}
User.init({ username: Sequelize.STRING }, { sequelize, tableName: 'users' });
typeorm 现代且支持 TypeScript。
// typeorm: 示例代码
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column()
username: string;
}
bookshelf 使用简单的对象扩展。
// bookshelf: 模型定义
const User = Bookshelf.Model.extend({
tableName: 'users',
hasTimestamps: true
});
objection 使用类继承。
// objection: 模型定义
class User extends Model {
static get tableName() {
return 'users';
}
static get relationMappings() {
return { posts: { model: Post, relation: Model.HasManyRelation } };
}
}
sequelize 使用初始化方法或类定义。
// sequelize: 模型定义
class User extends Model {}
User.init({
username: DataTypes.STRING
}, {
sequelize,
tableName: 'users'
});
typeorm 使用装饰器。
// typeorm: 模型定义
@Entity('users')
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column()
username: string;
}
bookshelf 使用链式查询。
// bookshelf: 查询数据
User.where({ username: 'alice' }).fetch()
.then(user => console.log(user));
objection 使用查询构建器。
// objection: 查询数据
const user = await User.query().findOne({ username: 'alice' });
sequelize 使用内置方法。
findAll、findOne。// sequelize: 查询数据
const user = await User.findOne({ where: { username: 'alice' } });
typeorm 使用 Repository 模式。
getRepository 获取操作对象。// typeorm: 查询数据
const user = await userRepository.findOne({ where: { username: 'alice' } });
bookshelf 需要显式定义关系。
hasMany、belongsTo。withRelated。// bookshelf: 关系查询
User.where({ id: 1 }).fetch({ withRelated: ['posts'] })
.then(user => console.log(user.related('posts')));
objection 使用 relationMappings。
withGraphFetched。// objection: 关系查询
const user = await User.query()
.findById(1)
.withGraphFetched('posts');
sequelize 使用 include 选项。
// sequelize: 关系查询
const user = await User.findOne({
where: { id: 1 },
include: [{ model: Post }]
});
typeorm 使用 Join 查询。
createQueryBuilder 或 find 选项。// typeorm: 关系查询
const user = await userRepository.findOne({
where: { id: 1 },
relations: ['posts']
});
bookshelf 无内置迁移工具。
// bookshelf: 迁移(使用 Knex)
// knex migrate:make create_users_table
objection 无内置迁移工具。
// objection: 迁移(使用 Knex)
// knex migrate:make create_users_table
sequelize 内置 CLI 迁移工具。
sequelize-cli。// sequelize: 迁移命令
// npx sequelize-cli migration:generate --name create-users
typeorm 内置迁移工具。
// typeorm: 迁移命令
// npm run typeorm -- migration:generate -- -n CreateUsers
bookshelf 支持 Knex.js 支持的数据库。
// bookshelf: 数据库配置
const knex = require('knex')({ client: 'mysql', connection: { ... } });
objection 支持 Knex.js 支持的数据库。
// objection: 数据库配置
const knex = require('knex')({ client: 'postgresql', connection: { ... } });
sequelize 原生支持多种数据库。
// sequelize: 数据库配置
const sequelize = new Sequelize('database', 'username', 'password', {
dialect: 'postgres'
});
typeorm 支持多种数据库。
// typeorm: 数据库配置
const dataSource = new DataSource({
type: 'postgres',
host: 'localhost',
// ...
});
| 特性 | bookshelf | objection | sequelize | typeorm |
|---|---|---|---|---|
| 维护状态 | ⚠️ 低频维护 | ✅ 活跃 | ✅ 稳定 | ✅ 活跃 |
| 模型定义 | 对象扩展 | 类继承 | 类/初始化 | 装饰器 |
| 查询风格 | 链式调用 | 查询构建器 | 方法调用 | Repository |
| TypeScript | ❌ 支持一般 | ✅ 支持良好 | ✅ 支持良好 | ✅ 原生支持 |
| 迁移工具 | 需 Knex | 需 Knex | 内置 CLI | 内置 CLI |
| 数据库 | Knex 支持 | Knex 支持 | 多种原生 | 多种原生 |
bookshelf 适合旧项目维护,新项目不推荐。
objection 适合需要灵活查询的场景。
sequelize 适合企业级稳定项目。
typeorm 适合 TypeScript 项目和现代架构。
这四个框架各有优劣,选择的关键在于你的项目需求和技术栈。
sequelize。typeorm。objection。bookshelf,但新项目避免使用。无论选择哪个,都要注意数据库设计的合理性,ORM 只是工具,良好的架构才是核心。
如果你的项目依赖旧版代码库且已稳定运行,可以继续使用 bookshelf。但在新项目中不建议选择,因为其社区活跃度下降,新功能支持有限。它基于 Knex.js,适合喜欢 SQL 查询构建器风格的开发者。
选择 objection 如果你需要灵活的 SQL 查询能力,同时想要 ORM 的便利。它基于 Knex.js,适合复杂查询场景。社区维护良好,文档清晰,适合中大型项目。
选择 sequelize 如果你需要稳定的、功能丰富的 ORM,支持多种数据库(MySQL、PostgreSQL 等)。它适合企业级应用,文档完善,社区庞大。如果你重视长期支持和稳定性,这是安全的选择。
选择 typeorm 如果你使用 TypeScript,或者喜欢装饰器语法。它支持 Active Record 和 Data Mapper 模式,适合现代 Node.js 项目。与 NestJS 等框架集成良好,适合追求开发体验的团队。
Bookshelf is a JavaScript ORM for Node.js, built on the Knex SQL query builder. It features both Promise-based and traditional callback interfaces, transaction support, eager/nested-eager relation loading, polymorphic associations, and support for one-to-one, one-to-many, and many-to-many relations.
It is designed to work with PostgreSQL, MySQL, and SQLite3.
Website and documentation. The project is hosted on GitHub, and has a comprehensive test suite.
Bookshelf aims to provide a simple library for common tasks when querying databases in JavaScript, and forming relations between these objects, taking a lot of ideas from the Data Mapper Pattern.
With a concise, literate codebase, Bookshelf is simple to read, understand, and extend. It doesn't force you to use any specific validation scheme, and provides flexible, efficient relation/nested-relation loading and first-class transaction support.
It's a lean object-relational mapper, allowing you to drop down to the raw Knex interface whenever you need a custom query that doesn't quite fit with the stock conventions.
You'll need to install a copy of Knex, and either mysql, pg, or sqlite3 from npm.
$ npm install knex
$ npm install bookshelf
# Then add one of the following:
$ npm install pg
$ npm install mysql
$ npm install sqlite3
The Bookshelf library is initialized by passing an initialized Knex client instance. The Knex documentation provides a number of examples for different databases.
// Setting up the database connection
const knex = require('knex')({
client: 'mysql',
connection: {
host : '127.0.0.1',
user : 'your_database_user',
password : 'your_database_password',
database : 'myapp_test',
charset : 'utf8'
}
})
const bookshelf = require('bookshelf')(knex)
// Defining models
const User = bookshelf.model('User', {
tableName: 'users'
})
This initialization should likely only ever happen once in your application. As it creates a connection pool for the current database, you should use the bookshelf instance returned throughout your library. You'll need to store this instance created by the initialize somewhere in the application so you can reference it. A common pattern to follow is to initialize the client in a module so you can easily reference it later:
// In a file named, e.g. bookshelf.js
const knex = require('knex')(dbConfig)
module.exports = require('bookshelf')(knex)
// elsewhere, to use the bookshelf client:
const bookshelf = require('./bookshelf')
const Post = bookshelf.model('Post', {
// ...
})
Here is an example to get you started:
const knex = require('knex')({
client: 'mysql',
connection: process.env.MYSQL_DATABASE_CONNECTION
})
const bookshelf = require('bookshelf')(knex)
const User = bookshelf.model('User', {
tableName: 'users',
posts() {
return this.hasMany(Posts)
}
})
const Post = bookshelf.model('Post', {
tableName: 'posts',
tags() {
return this.belongsToMany(Tag)
}
})
const Tag = bookshelf.model('Tag', {
tableName: 'tags'
})
new User({id: 1}).fetch({withRelated: ['posts.tags']}).then((user) => {
console.log(user.related('posts').toJSON())
}).catch((error) => {
console.error(error)
})
.set() on a model.Model, adding timestamps, attribute validation and some native CRUD methods.Have questions about the library? Come join us in the #bookshelf freenode IRC channel for support on knex.js and bookshelf.js, or post an issue on Stack Overflow.
If you want to contribute to Bookshelf you'll usually want to report an issue or submit a pull-request. For this purpose the online repository is available on GitHub.
For further help setting up your local development environment or learning how you can contribute to Bookshelf you should read the Contributing document available on GitHub.
Yes, you can call .asCallback(function(err, resp) { on any database operation method and use the standard (err, result) style callback interface if you prefer.
Make sure to check that the type is correct for the initial parameters passed to the initial model being fetched. For example new Model({id: '1'}).load([relations...]) will not return the same as new Model({id: 1}).load([relations...]) - notice that the id is a string in one case and a number in the other. This can be a common mistake if retrieving the id from a url parameter.
This is only an issue if you're eager loading data with load without first fetching the original model. new Model({id: '1'}).fetch({withRelated: [relations...]}) should work just fine.
The issue here is that Knex, the database abstraction layer used by Bookshelf, uses connection pooling and thus keeps the database connection open. If you want your process to exit after your script has finished, you will have to call .destroy(cb) on the knex property of your Bookshelf instance or on the Knex instance passed during initialization. More information about connection pooling can be found over at the Knex docs.
If you pass debug: true in the options object to your knex initialize call, you can see all of the query calls being made. You can also pass that same option to all methods that access the database, like model.fetch() or model.destroy(). Examples:
// Turning on debug mode for all queries
const knex = require('knex')({
debug: true,
client: 'mysql',
connection: process.env.MYSQL_DATABASE_CONNECTION
})
const bookshelf = require('bookshelf')(knex)
// Debugging a single query
new User({id: 1}).fetch({debug: true, withRelated: ['posts.tags']}).then(user => {
// ...
})
Sometimes you need to dive a bit further into the various calls and see what all is going on behind the scenes. You can use node-inspector, which allows you to debug code with debugger statements like you would in the browser.
Bookshelf uses its own copy of the bluebird Promise library. You can read up here for more on debugging Promises.
Adding the following block at the start of your application code will catch any errors not otherwise caught in the normal Promise chain handlers, which is very helpful in debugging:
process.stderr.on('data', (data) => {
console.log(data)
})
See the CONTRIBUTING document on GitHub.
While it primarily targets Node.js, all dependencies are browser compatible, and it could be adapted to work with other javascript environments supporting a sqlite3 database, by providing a custom Knex adapter. No such adapter exists though.
We found the following projects using Bookshelf, but there can be more: