bookshelf vs knex vs sequelize vs typeorm
Choosing the Right Database Layer for Node.js Applications
bookshelfknexsequelizetypeormSimilar Packages:

Choosing the Right Database Layer for Node.js Applications

bookshelf, knex, sequelize, and typeorm are all tools designed to help JavaScript developers interact with SQL databases, but they solve different parts of the problem. knex is a query builder that gives you full control over SQL generation without forcing an object model. bookshelf is an ORM built on top of knex that adds model relationships and events. sequelize is a mature, promise-based ORM for Node.js that supports multiple databases and offers a rich feature set for defining models and associations. typeorm is a modern ORM that heavily leverages TypeScript decorators and decorators to define entities, supporting both Active Record and Data Mapper patterns. While bookshelf and knex offer flexibility, sequelize and typeorm provide more structure and automation for complex domain models.

Npm Package Weekly Downloads Trend

3 Years

Github Stars Ranking

Stat Detail

Package
Downloads
Stars
Size
Issues
Publish
License
bookshelf06,347-2396 years agoMIT
knex020,344941 kB7452 months agoMIT
sequelize030,3702.91 MB1,0846 months agoMIT
typeorm036,64621.7 MB6252 days agoMIT

Database Layers in Node.js: Knex, Bookshelf, Sequelize, and TypeORM Compared

When building backend services in Node.js, choosing how to talk to your database is one of the most critical architectural decisions. The four packages β€” knex, bookshelf, sequelize, and typeorm β€” represent different philosophies on how to manage data. Some give you raw power and control, while others offer structure and automation. Let's break down how they handle real-world engineering tasks.

πŸ› οΈ Defining Models: Plain Objects vs Classes vs Decorators

How you define your data structure sets the tone for the rest of your application.

knex does not have a concept of models. You work directly with tables and queries. This means no abstraction layer between you and the database schema.

// knex: No models, just tables
const users = await knex('users').where('active', true);

bookshelf uses a model definition that extends a base class, relying on knex under the hood. It feels traditional and explicit.

// bookshelf: Model definition
const User = bookshelf.Model.extend({
  tableName: 'users',
  hasTimestamps: true
});

const user = await User.where('active', true).fetch();

sequelize defines models using define or class syntax, attaching them to a Sequelize instance. It supports validation and hooks directly in the model definition.

// sequelize: Model definition
const User = sequelize.define('User', {
  firstName: DataTypes.STRING,
  active: DataTypes.BOOLEAN
});

const users = await User.findAll({ where: { active: true } });

typeorm uses TypeScript classes with decorators to define entities. This approach integrates tightly with the type system and IDE tooling.

// typeorm: Entity with decorators
@Entity()
class User {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  firstName: string;

  @Column({ default: true })
  active: boolean;
}

const users = await userRepository.find({ where: { active: true } });

πŸ”— Handling Relationships: Joins vs Eager Loading

Real applications rarely deal with single tables. How each library handles joins and related data varies widely.

knex requires you to manually write joins. You have full control but must manage the complexity yourself.

// knex: Manual join
const posts = await knex('posts')
  .join('users', 'posts.user_id', 'users.id')
  .select('posts.title', 'users.name');

bookshelf handles relationships via methods on the model. It uses knex to generate the joins when you fetch with relations.

// bookshelf: Relationship definition and fetch
const User = bookshelf.Model.extend({
  tableName: 'users',
  posts: function() {
    return this.hasMany('Post');
  }
});

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

sequelize uses include to eagerly load associations. It automatically generates the necessary joins based on your model setup.

// sequelize: Eager loading
const user = await User.findOne({
  where: { id: 1 },
  include: [{ model: Post }]
});

typeorm uses the relations option in repository queries or joins in query builders. It respects the relationships defined in your entity decorators.

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

πŸ”„ Migrations: Building Schema Over Time

Changing database structure without losing data is a core requirement for production apps.

knex has a built-in migration system that uses JavaScript or TypeScript files to define up and down steps. It is widely considered one of the most flexible migration tools.

// knex: Migration file
exports.up = function(knex) {
  return knex.schema.createTable('users', (table) => {
    table.increments();
    table.string('name');
  });
};

exports.down = function(knex) {
  return knex.schema.dropTable('users');
};

bookshelf does not have its own migration system. It relies entirely on knex for schema changes, so you use the same migration files as above.

// bookshelf: Uses knex migrations (same as above)
// No separate migration API

sequelize includes a CLI tool (sequelize-cli) to generate and run migrations. It tracks migration history in a dedicated table.

// sequelize: Migration file
module.exports = {
  up: (queryInterface, Sequelize) => {
    return queryInterface.createTable('Users', {
      id: { type: Sequelize.INTEGER, primaryKey: true, autoIncrement: true },
      name: Sequelize.STRING
    });
  },
  down: (queryInterface) => queryInterface.dropTable('Users')
};

typeorm can generate migrations automatically from your entity changes or allow you to write them manually. It keeps a migration log in the database.

// typeorm: Generated migration
export class CreateUsers1620000000000 implements MigrationInterface {
  public async up(queryRunner: QueryRunner): Promise<void> {
    await queryRunner.query(`CREATE TABLE "users" ("id" int PRIMARY KEY, "name" varchar)`);
  }
  public async down(queryRunner: QueryRunner): Promise<void> {
    await queryRunner.query(`DROP TABLE "users"`);
  }
}

⚠️ Maintenance and Future-Proofing

Before committing to a library, you must consider its long-term viability.

bookshelf has seen very little activity in recent years. The repository shows long gaps between releases, and many in the community consider it to be in maintenance mode at best. For new projects, this lack of active development is a significant risk. You might find yourself stuck with unresolved bugs or compatibility issues with newer Node.js versions.

knex remains actively maintained and is widely used as a dependency by other ORMs. Its focus on being a query builder rather than a full ORM keeps its scope manageable and its stability high. It is a safe choice for the long haul.

sequelize continues to receive regular updates and has a large community. It is stable and widely adopted in enterprise environments. While its API can feel heavy at times, its commitment to backward compatibility and broad database support makes it a reliable option.

typeorm is actively developed with a strong focus on TypeScript. It evolves alongside the TypeScript ecosystem and frequently adopts new language features. Its active issue tracker and regular releases signal a healthy project trajectory.

πŸ“Š Summary: Key Differences

Featureknexbookshelfsequelizetypeorm
TypeQuery BuilderORM (on Knex)ORMORM (TS-focused)
Model DefinitionNoneClass extensionDefine/ClassDecorators/Classes
RelationshipsManual JoinshasMany/belongsToincluderelations option
MigrationsBuilt-in (JS/TS)Via KnexCLI + JS/TSAuto-gen or Manual (TS)
Active Maintenanceβœ… Yes⚠️ Low activityβœ… Yesβœ… Yes
Best ForControl, PerformanceLegacy, Knex usersEnterprise, Multi-DBTypeScript, Modern Stacks

πŸ’‘ The Big Picture

knex is your scalpel β€” precise, powerful, and requiring skill to wield. Choose it when you need to hand-craft queries or when an ORM gets in the way.

bookshelf is a legacy tool that pairs well with knex if you need basic ORM features. However, due to its low maintenance status, avoid it for new projects unless you are maintaining an existing codebase.

sequelize is the workhorse β€” robust, feature-complete, and ready for complex enterprise needs. It is ideal when you need to support multiple databases and want a proven track record.

typeorm is the modern architect's choice for TypeScript shops. It offers a clean, type-safe developer experience and fits naturally into modern NestJS or Express architectures.

Final Thought: If you are starting fresh today with TypeScript, typeorm offers the smoothest path. If you need maximum SQL control or are supporting many database dialects, knex or sequelize remain unbeatable. Avoid bookshelf for new initiatives due to its stagnation.

How to Choose: bookshelf vs knex vs sequelize vs typeorm

  • bookshelf:

    Choose bookshelf if you need an ORM with strong relationship handling but want to keep knex as your underlying query engine. It is ideal for legacy projects or teams that prefer explicit model definitions with event hooks. However, be aware that development has slowed significantly, and it may not be the best choice for new greenfield projects requiring long-term active maintenance.

  • knex:

    Choose knex if you want maximum control over your SQL queries without the overhead of a full ORM. It is perfect for applications where performance is critical, or where the data structure does not fit neatly into object models. Use it when you need a robust migration system and a flexible query builder that works across different SQL dialects without locking you into a specific data pattern.

  • sequelize:

    Choose sequelize if you need a battle-tested, feature-rich ORM for Node.js that supports a wide range of SQL databases. It is excellent for projects that rely on complex associations, transactions, and eager loading out of the box. Its strong community and extensive documentation make it a safe bet for enterprise applications where stability and widespread knowledge are priorities.

  • typeorm:

    Choose typeorm if your project is built with TypeScript and you want to leverage decorators for defining entities and relationships. It is well-suited for developers who prefer the Data Mapper pattern or need support for both Active Record and repository patterns. Its strong integration with modern TypeScript features makes it a top choice for new, type-safe backend architectures.

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.