bookshelf vs objection vs sequelize vs typeorm
Choosing an ORM for Node.js Backends
bookshelfobjectionsequelizetypeormSimilar Packages:

Choosing an ORM for Node.js Backends

Object-Relational Mappers (ORMs) bridge the gap between JavaScript code and SQL databases. sequelize, typeorm, objection, and bookshelf are four popular choices in the Node.js ecosystem. sequelize and typeorm are full-featured ORMs with active record patterns, while objection and bookshelf are built on top of the knex query builder, offering a different approach to data modeling. This comparison helps full-stack developers select the right tool for serverless functions, API routes, or dedicated backend services.

Npm Package Weekly Downloads Trend

3 Years

Github Stars Ranking

Stat Detail

Package
Downloads
Stars
Size
Issues
Publish
License
bookshelf06,352-2386 years agoMIT
objection07,341645 kB1292 years agoMIT
sequelize030,3512.91 MB1,0323 months agoMIT
typeorm036,52421.6 MB5449 days agoMIT

ORM Showdown: Bookshelf vs Objection vs Sequelize vs TypeORM

When building a backend in Node.js β€” whether for serverless functions, API routes, or a dedicated service β€” you need a reliable way to talk to your database. Object-Relational Mappers (ORMs) let you interact with database tables using JavaScript classes instead of raw SQL. While sequelize and typeorm are full-featured standalone ORMs, objection and bookshelf sit on top of the knex query builder. Let's break down how they handle real-world tasks.

πŸ—οΈ Defining Data Models

How you define your tables sets the tone for the rest of your code. Some tools use classes, others use configuration objects, and some rely on decorators.

sequelize uses a class-based initialization or a define method.

  • You specify column types and options directly in the model definition.
  • Supports both v6 and v7 syntax styles.
// sequelize: Model definition
const User = sequelize.define('User', {
  firstName: DataTypes.STRING,
  lastName: DataTypes.STRING
});

typeorm relies heavily on TypeScript decorators.

  • You decorate a class to mark it as an entity.
  • Columns are defined with type metadata.
// typeorm: Entity definition
@Entity()
export class User {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  firstName: string;
}

objection extends a base Model class.

  • You must specify the tableName property.
  • It uses knex under the hood for query building.
// objection: Model class
class User extends Model {
  static get tableName() {
    return 'users';
  }
}

bookshelf extends bookshelf.Model.

  • Similar to objection but with an older API style.
  • Requires a knex instance to be passed during setup.
// bookshelf: Model definition
const User = bookshelf.Model.extend({
  tableName: 'users'
});

πŸ” Fetching Records

Reading data is the most common operation. The syntax varies from promise-based chains to repository patterns.

sequelize uses static methods on the model.

  • Methods like findOne or findAll return promises.
  • Options object controls filtering and limits.
// sequelize: Fetching
const user = await User.findOne({
  where: { lastName: 'Smith' }
});

typeorm uses a Repository pattern.

  • You get a repository instance from the connection manager.
  • Methods like find or findOneBy are used.
// typeorm: Fetching
const user = await userRepository.findOneBy({
  lastName: 'Smith'
});

objection chains query methods.

  • Starts with query() on the model class.
  • Feels very close to writing SQL.
// objection: Fetching
const user = await User.query().findOne({
  lastName: 'Smith'
});

bookshelf uses instance methods.

  • You often create a new model instance before fetching.
  • Uses .fetch() to execute the query.
// bookshelf: Fetching
const user = await new User({ lastName: 'Smith' }).fetch();

πŸ”— Handling Relationships

Real apps rarely have just one table. Joining users to posts or orders is critical.

sequelize defines associations explicitly.

  • Use hasMany, belongsTo, etc., after defining models.
  • Eager loading is done via the include option.
// sequelize: Relations
User.hasMany(Post);
const user = await User.findOne({
  where: { id: 1 },
  include: [Post]
});

typeorm defines relations inside the entity class.

  • Decorators like @OneToMany describe the link.
  • Relations are loaded via relations option or joins.
// typeorm: Relations
@OneToMany(() => Post, post => post.user)
posts: Post[];

// Fetching with relations
const user = await userRepository.findOne({
  where: { id: 1 },
  relations: ['posts']
});

objection uses a static relationMappings object.

  • Keeps relation logic separate from instance data.
  • Uses withGraphFetched to load related data.
// objection: Relations
static get relationMappings() {
  return {
    posts: {
      relation: Model.HasManyRelation,
      modelClass: Post,
      join: { from: 'users.id', to: 'posts.userId' }
    }
  };
}

// Fetching
const user = await User.query().findById(1).withGraphFetched('posts');

bookshelf defines relations as methods on the model.

  • Return a this.hasMany or this.belongsTo call.
  • Uses .fetch({ withRelated: [...] }) to load.
// bookshelf: Relations
posts() {
  return this.hasMany(Post);
}

// Fetching
const user = await new User({ id: 1 }).fetch({
  withRelated: ['posts']
});

πŸ›‘οΈ TypeScript Support

For frontend developers moving to full-stack, type safety is non-negotiable.

sequelize has improved significantly in recent versions.

  • Requires generic types for model definitions.
  • Can be verbose but provides strong safety.
// sequelize: TS usage
interface UserCreationAttrs {
  firstName: string;
}
const User = sequelize.define<User, UserCreationAttrs>('User', {...});

typeorm is built with TypeScript in mind.

  • Decorators provide metadata automatically.
  • Entities are standard TypeScript classes.
// typeorm: TS usage
@Entity()
export class User {
  @PrimaryGeneratedColumn()
  id: number; // Type is inferred
}

objection supports TypeScript via generics.

  • You can pass types to the Model class.
  • Query builder types are robust due to knex.
// objection: TS usage
class User extends Model {
  id!: number;
  firstName!: string;
}

bookshelf has weaker TypeScript support.

  • Definitions often require manual type casting.
  • Community typings exist but are less maintained.
// bookshelf: TS usage
// Often requires 'any' or complex interface extensions
interface User extends bookshelf.Model<User> {
  firstName(): string;
}

πŸ“… Maintenance and Future Proofing

Choosing a library is a long-term commitment. You want to know the team behind it is still active.

sequelize is highly active.

  • Regular releases and security patches.
  • Large community ensures long-term viability.

typeorm is highly active.

  • Frequent updates and strong corporate backing.
  • Widely used in the NestJS ecosystem.

objection is actively maintained.

  • Keeps pace with knex updates.
  • Preferred modern choice for knex users.

bookshelf is in maintenance mode.

  • Release cycles are slow compared to others.
  • Community momentum has shifted toward objection.
  • ⚠️ Recommendation: Avoid for new projects. Evaluate objection instead if you need knex.

πŸ“Š Summary Table

Featuresequelizetypeormobjectionbookshelf
PatternActive RecordActive Record / Data MapperActive Record on KnexActive Record on Knex
Setupsequelize.defineDecorators @EntityClass extends Modelbookshelf.Model.extend
Query StyleStatic MethodsRepositoryQuery Builder ChainInstance Methods
TypeScriptGood (Generics)Excellent (Decorators)Good (Generics)Weak
Statusβœ… Activeβœ… Activeβœ… Active⚠️ Maintenance

πŸ’‘ The Big Picture

sequelize is the reliable workhorse 🐴. It has been around for years, handles complex SQL dialects well, and is a safe bet for traditional backends. If you want something that just works with PostgreSQL or MySQL without extra configuration, this is it.

typeorm is the TypeScript native πŸ“˜. If your team lives in TypeScript and uses decorators, this feels the most natural. It is also the default choice if you are using NestJS.

objection is the flexible query builder πŸ› οΈ. It gives you the power of knex with the convenience of models. Choose this if you find standard ORMs too restrictive and want to write queries that feel closer to SQL.

bookshelf is the legacy option πŸ•°οΈ. While it still works, the community has largely moved to objection for knex-based projects. Start with objection if you need that specific architecture.

Final Thought: For most new full-stack projects today, typeorm or sequelize will offer the smoothest experience. If you need raw query power, pick objection. Skip bookshelf for new development to avoid future migration headaches.

How to Choose: bookshelf vs objection vs sequelize vs typeorm

  • bookshelf:

    Choose bookshelf only if you are maintaining a legacy system already using it. It is built on knex but has seen significantly slower development activity compared to modern alternatives. For new projects, the community generally recommends objection for a knex-based workflow due to better TypeScript support and active maintenance.

  • objection:

    Choose objection if you want the flexibility of the knex query builder with a cleaner model layer. It is ideal for developers who prefer writing SQL-like queries in JavaScript without giving up model relationships. It shines in projects where you need fine-grained control over queries but still want object mapping.

  • sequelize:

    Choose sequelize if you need a mature, batteries-included ORM with a strong focus on SQL dialects like PostgreSQL, MySQL, and SQLite. It is well-suited for teams that want a standard Active Record pattern with built-in migrations and validation. It is a safe, stable choice for traditional Node.js backends.

  • typeorm:

    Choose typeorm if you are heavily invested in TypeScript and prefer using decorators to define entities. It supports both SQL and NoSQL (MongoDB) databases, making it flexible for polyglot persistence. It is often the top pick for NestJS applications and modern full-stack frameworks.

README for bookshelf

bookshelf.js

NPM Version Build Status Dependency Status devDependency Status

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.

Introduction

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.

Installation

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', {
  // ...
})

Examples

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)
})

Official Plugins

  • Virtuals: Define virtual properties on your model to compute new values.
  • Case Converter: Handles the conversion between the database's snake_cased and a model's camelCased properties automatically.
  • Processor: Allows defining custom processor functions that handle transformation of values whenever they are .set() on a model.

Community plugins

  • bookshelf-cascade-delete - Cascade delete related models on destroy.
  • bookshelf-json-columns - Parse and stringify JSON columns on save and fetch instead of manually define hooks for each model (PostgreSQL and SQLite).
  • bookshelf-mask - Similar to the functionality of the {@link Model#visible} attribute but supporting multiple scopes, masking models and collections using the json-mask API.
  • bookshelf-schema - A plugin for handling fields, relations, scopes and more.
  • bookshelf-signals - A plugin that translates Bookshelf events to a central hub.
  • bookshelf-paranoia - Protect your database from data loss by soft deleting your rows.
  • bookshelf-uuid - Automatically generates UUIDs for your models.
  • bookshelf-modelbase - An alternative to extend Model, adding timestamps, attribute validation and some native CRUD methods.
  • bookshelf-advanced-serialization - A more powerful visibility plugin, supporting serializing models and collections according to access permissions, application context, and after ensuring relations have been loaded.
  • bookshelf-plugin-mode - Plugin inspired by the functionality of the {@link Model#visible} attribute, allowing to specify different modes with corresponding visible/hidden fields of model.
  • bookshelf-secure-password - A plugin for easily securing passwords using bcrypt.
  • bookshelf-default-select - Enables default column selection for models. Inspired by the functionality of the {@link Model#visible} attribute, but operates on the database level.
  • bookshelf-ez-fetch - Convenient fetching methods which allow for compact filtering, relation selection and error handling.
  • bookshelf-manager - Model & Collection manager to make it easy to create & save deep, nested JSON structures from API requests.

Support

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.

Contributing

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.

F.A.Q.

Can I use standard node.js style callbacks?

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.

My relations don't seem to be loading, what's up?

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.

My process won't exit after my script is finished, why?

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.

How do I debug?

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)
})

How do I run the test suite?

See the CONTRIBUTING document on GitHub.

Can I use Bookshelf outside of Node.js?

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.

Which open-source projects are using Bookshelf?

We found the following projects using Bookshelf, but there can be more:

  • Ghost (A blogging platform) uses bookshelf. [Link]
  • Soapee (Soap Making Community and Resources) uses bookshelf. [Link]
  • NodeZA (Node.js social platform for developers in South Africa) uses bookshelf. [Link]
  • Sunday Cook (A social cooking event platform) uses bookshelf. [Link]
  • FlyptoX (Open-source Node.js cryptocurrency exchange) uses bookshelf. [Link]
  • And of course, everything on here use bookshelf too.