knex is a SQL query builder that provides a flexible interface for constructing database queries without enforcing a specific data model structure. sequelize is a full-featured Promise-based ORM (Object-Relational Mapping) for Node.js that supports multiple SQL dialects and includes built-in migrations and validations. bookshelf is an ORM built on top of knex that focuses on relational data mapping and model relationships. waterline is a datastore-agnostic ORM originally designed for the Sails.js framework, supporting both SQL and NoSQL databases, though its standalone usage has declined significantly.
When building backend services in Node.js, choosing the right data access layer shapes your entire architecture. The four packages β knex, sequelize, bookshelf, and waterline β represent different points on the spectrum from raw SQL control to full ORM abstraction. Let's examine how they handle real-world engineering challenges.
knex acts as a query builder only β it helps you write SQL safely but doesn't define models or relationships.
// knex: Build queries manually
const users = await knex('users')
.where('active', true)
.leftJoin('posts', 'users.id', 'posts.user_id')
.select('users.name', 'posts.title');
sequelize provides a complete ORM with model definitions, associations, and built-in migrations.
// sequelize: Define models and relationships
const User = sequelize.define('User', { name: DataTypes.STRING });
const Post = sequelize.define('Post', { title: DataTypes.STRING });
User.hasMany(Post);
const users = await User.findAll({
where: { active: true },
include: [Post]
});
bookshelf sits between them β it uses knex under the hood but adds model and relationship layers.
// bookshelf: Models with knex flexibility
const User = bookshelf.Model.extend({
tableName: 'users',
posts: function() { return this.hasMany('Post'); }
});
const users = await User.where({ active: true }).fetch({ withRelated: ['posts'] });
waterline attempts to be datastore-agnostic, working with both SQL and NoSQL, but requires adapter configuration.
// waterline: Adapter-based models
const User = Waterline.Collection.extend({
identity: 'user',
datastore: 'postgres',
attributes: {
name: { type: 'string' },
posts: { collection: 'post' }
}
});
const users = await User.find({ active: true }).populate('posts');
knex includes a migration system but requires manual setup and configuration files.
// 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');
};
sequelize has integrated migration support with CLI tools that auto-generate files from model changes.
// sequelize migration generated by CLI
module.exports = {
up: async (queryInterface, Sequelize) => {
await queryInterface.createTable('Users', {
id: { type: Sequelize.INTEGER, primaryKey: true, autoIncrement: true },
name: { type: Sequelize.STRING }
});
},
down: async (queryInterface) => {
await queryInterface.dropTable('Users');
}
};
bookshelf relies entirely on knex for migrations since it builds on top of it β no separate migration system.
// bookshelf uses same knex migrations as shown above
// No additional migration code needed
waterline historically used auto-migration (auto-create/alter tables) which is discouraged in production. Modern usage requires manual migration scripts outside the ORM.
// waterline: Auto-migration (NOT recommended for production)
// In config: migrate: 'alter' (dev only)
// Production requires custom migration scripts
knex requires you to manually write joins and map results β no automatic relationship handling.
// knex: Manual join for relationships
const usersWithPosts = await knex('users')
.leftJoin('posts', 'users.id', 'posts.user_id')
.select('users.*', 'posts.title as post_title');
// Must manually group results by user if needed
sequelize defines relationships in models and automatically handles joins through include.
// sequelize: Declarative associations
const User = sequelize.define('User', { /* ... */ });
const Post = sequelize.define('Post', { /* ... */ });
User.hasMany(Post);
const user = await User.findByPk(1, { include: [Post] });
// Returns user with posts nested automatically
bookshelf defines relationships in model methods and fetches them with withRelated.
// bookshelf: Relationship methods
const User = bookshelf.Model.extend({
tableName: 'users',
posts: function() { return this.hasMany('Post'); }
});
const user = await User.where({ id: 1 }).fetch({ withRelated: ['posts'] });
// Access via user.related('posts')
waterline uses collection references and populate() for relationships, abstracting the join mechanism.
// waterline: Populate relationships
const user = await User.findOne(1).populate('posts');
// Works across SQL and NoSQL adapters uniformly
knex gives you direct access to raw SQL when needed while maintaining query safety.
// knex: Mix builder with raw SQL
const result = await knex.raw(
'SELECT COUNT(*) FROM users WHERE created_at > ?',
[lastWeek]
);
// Or within builder:
const users = await knex('users').whereRaw('age > ?', [18]);
sequelize allows raw queries but encourages model usage; escaping is handled automatically.
// sequelize: Raw queries with model context
const [results] = await sequelize.query(
'SELECT * FROM users WHERE age > :age',
{ replacements: { age: 18 }, model: User, mapToModel: true }
);
bookshelf inherits knex's raw SQL capabilities through its underlying query builder.
// bookshelf: Access knex directly for raw operations
const result = await bookshelf.knex.raw('SELECT version()');
waterline limits raw SQL access due to its datastore-agnostic design β some adapters don't support raw queries at all.
// waterline: Limited raw SQL support
// Depends on adapter; many NoSQL adapters have no SQL equivalent
knex provides no validation or hooks β you must implement these in your application layer.
// knex: No built-in validation
// Must add manual checks before inserting
if (!email.includes('@')) throw new Error('Invalid email');
await knex('users').insert({ email });
sequelize includes model-level validations and lifecycle hooks (beforeSave, afterCreate, etc.).
// sequelize: Built-in validations and hooks
const User = sequelize.define('User', {
email: {
type: DataTypes.STRING,
validate: { isEmail: true }
}
}, {
hooks: {
beforeCreate: async (user) => {
user.password = await hashPassword(user.password);
}
}
});
bookshelf supports basic validation through plugins and has event emitters for lifecycle hooks.
// bookshelf: Plugin-based validation and events
const User = bookshelf.Model.extend({
tableName: 'users',
initialize: function() {
this.on('creating', async (model) => {
// Custom hook logic
});
}
});
waterline includes built-in validations and lifecycle callbacks similar to ORMs but varies by adapter.
// waterline: Validation rules in attributes
const User = Waterline.Collection.extend({
attributes: {
email: { type: 'string', required: true, isEmail: true }
},
beforeCreate: async (values, proceed) => {
// Hook logic
proceed();
}
});
knex supports major SQL databases (PostgreSQL, MySQL, SQLite, Oracle, MSSQL) but not NoSQL.
// knex: SQL-only clients
const knexPg = knex({ client: 'pg', connection: { /* ... */ } });
const knexMysql = knex({ client: 'mysql2', connection: { /* ... */ } });
sequelize supports SQL databases only (PostgreSQL, MySQL, MariaDB, SQLite, MSSQL).
// sequelize: SQL dialects
const sequelizePg = new Sequelize(database, user, pass, { dialect: 'postgres' });
const sequelizeMysql = new Sequelize(database, user, pass, { dialect: 'mysql' });
bookshelf inherits knex's SQL support β no NoSQL capability.
// bookshelf: Same SQL support as knex
const bookshelfPg = bookshelf(knex({ client: 'pg' }));
waterline uniquely supports both SQL and NoSQL through adapters (MongoDB, Redis, etc.), though adapter quality varies.
// waterline: Multi-store adapters
const sailsMongo = require('sails-mongo');
const sailsPostgres = require('sails-postgresql');
// Configure different datastores per model
knex remains actively maintained with regular updates and strong community adoption for query building tasks.
sequelize is actively developed with frequent releases, comprehensive documentation, and enterprise usage.
bookshelf has slower release cycles but remains stable for projects already using it; community activity is moderate.
waterline is primarily maintained for Sails.js framework compatibility; standalone usage is deprecated in favor of more specialized tools. Official documentation notes limited active development for non-Sails use cases.
| Feature | knex | sequelize | bookshelf | waterline |
|---|---|---|---|---|
| Type | Query Builder | Full ORM | ORM on Knex | Datastore-Agnostic ORM |
| Relationships | Manual Joins | Declarative Associations | Model Methods + withRelated | Populate() |
| Migrations | Built-in CLI | Built-in CLI + Auto-sync | Uses Knex Migrations | Auto-migrate (dev only) |
| Validation | None (manual) | Built-in | Plugin-based | Built-in |
| Raw SQL | Full Support | Supported | Full Support (via Knex) | Limited/Adapter-Dependent |
| Database Types | SQL Only | SQL Only | SQL Only | SQL + NoSQL |
| Best For | Custom Queries, Performance | Rapid CRUD Development | Relational Mapping + Flex | Legacy Sails/NoSQL Needs |
knex is your go-to when you need precise control over SQL without ORM constraints β perfect for analytics, complex reporting, or migrating legacy systems where schema doesn't fit neat models.
sequelize shines in standard business applications where development speed matters more than query optimization β ideal for startups building MVPs or teams valuing convention over configuration.
bookshelf offers a middle ground for projects that need relationship mapping but still want to drop down to raw SQL when necessary β great for read-heavy APIs with nested resources.
waterline should only be considered for maintaining existing Sails.js applications or specific multi-store requirements; for new projects, modern alternatives provide better long-term support.
Final Thought: Your choice depends on how much abstraction you need versus how much control you want. Start with knex if you're unsure β you can always add modeling layers later, but removing ORM constraints is much harder.
Avoid using waterline for new projects as it is primarily maintained for legacy Sails.js applications and has limited active development for standalone use. Consider it only if you are maintaining an existing Sails.js codebase or require datastore agnosticism across SQL and NoSQL systems in a specific legacy context. For modern applications, evaluate sequelize or knex based on your database requirements instead.
Choose bookshelf if you need an ORM that combines the flexibility of knex with built-in relationship handling (hasOne, hasMany, manyToMany). It is suitable for projects that require complex relational data mapping but still want direct access to underlying query builder capabilities. Ideal when you need more structure than raw knex but less opinionation than sequelize, particularly for read-heavy applications with deep nested relationships.
Choose knex if you need maximum control over your SQL queries and prefer to define your own data models without ORM overhead. It is ideal for projects requiring complex joins, raw SQL performance, or when working with legacy databases where schema constraints don't match standard ORM patterns. Use it when you want a lightweight layer that handles connection pooling and query building but leaves data mapping to your application logic.
Choose sequelize if you want a batteries-included ORM with built-in support for migrations, validations, and associations across multiple SQL databases. It works well for teams that prefer defining models in code rather than managing raw SQL, and need features like automatic table synchronization, hooks, and eager loading. Best suited for standard CRUD applications where development speed and convention over configuration are priorities.

Waterline is a next-generation storage and retrieval engine, and the default ORM used in the Sails framework.
It provides a uniform API for accessing stuff from different kinds of databases and protocols. That means you write the same code to get and store things like users, whether they live in MySQL, MongoDB, neDB, or Postgres.
Waterline strives to inherit the best parts of ORMs like ActiveRecord, Hibernate, and Mongoose, but with a fresh perspective and emphasis on modularity, testability, and consistency across adapters.
Starting with v0.13, Waterline takes full advantage of ECMAScript & Node 8's await keyword.
In other words, no more callbacks.
var newOrg = await Organization.create({
slug: 'foo'
})
.fetch();
Looking for the version of Waterline used in Sails v0.12? See the 0.11.x branch of this repo. If you're upgrading to v0.13 from a previous release of Waterline standalone, take a look at the upgrading guide.
Install from NPM.
$ npm install waterline
Waterline uses the concept of an adapter to translate a predefined set of methods into a query that can be understood by your data store. Adapters allow you to use various datastores such as MySQL, PostgreSQL, MongoDB, Redis, etc. and have a clear API for working with your model data.
Waterline supports a wide variety of adapters, both core and community maintained.
The up-to-date documentation for Waterline is maintained on the Sails framework website. You can find detailed API reference docs under Reference > Waterline ORM. For conceptual info (including Waterline standalone usage), and answers to common questions, see Concepts > Models & ORM.
Check out the recommended community support options for tutorials and other resources. If you have a specific question, or just need to clarify how something works, ask for help or reach out to the core team directly.
You can keep up to date with security patches, the Waterline release schedule, new database adapters, and events in your area by following us (@sailsjs) on Twitter.
To report a bug, click here.
Please observe the guidelines and conventions laid out in our contribution guide when opening issues or submitting pull requests.
All tests are written with mocha and should be run with npm:
$ npm test
MIT. Copyright Β© 2012-present Mike McNeil & The Sails Company
Waterline, like the rest of the Sails framework, is free and open-source under the MIT License.
