Bookshelf is a JavaScript ORM for Node.js built on the Knex SQL query builder. It provides 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.
npm install bookshelf knex (plus database driver: pg, mysql, or sqlite3)const knex = require('knex')(dbConfig);
const bookshelf = require('bookshelf')(knex);const knex = require('knex')({
client: 'mysql',
connection: {
host: '127.0.0.1',
user: 'your_db_user',
password: 'your_password',
database: 'myapp'
}
});
const bookshelf = require('bookshelf')(knex);
// Define a model
const User = bookshelf.model('User', {
tableName: 'users',
posts() {
return this.hasMany('Post');
}
});
const Post = bookshelf.model('Post', {
tableName: 'posts',
user() {
return this.belongsTo('User');
}
});
// Use the models
const user = await new User({name: 'Alice'}).save();
const userWithPosts = await new User({id: 1}).fetch({withRelated: ['posts']});Bookshelf is built around several key components:
Core Bookshelf instance initialization and configuration, including model registry and plugin system.
/**
* Creates a new Bookshelf instance
* @param {Knex} knex - Initialized Knex client instance
* @returns {Bookshelf} Bookshelf instance
*/
function Bookshelf(knex);
// Instance properties and methods
interface BookshelfInstance {
VERSION: string;
knex: Knex;
Model: ModelConstructor;
Collection: CollectionConstructor;
model(name: string): Model;
model(name: string, Model: ModelConstructor, staticProperties?: object): Model;
collection(name: string): Collection;
collection(name: string, Collection: CollectionConstructor, staticProperties?: object): Collection;
resolve(name: string): any;
transaction(callback: (transaction: Transaction) => Promise<any>): Promise<any>;
plugin(plugin: string | Function | Array, options?: any): BookshelfInstance;
}Complete CRUD operations, query building, and attribute management for individual database records.
// Static methods
interface ModelStatic {
forge(attributes?: object, options?: object): Model;
collection(models?: Model[], options?: object): Collection;
count(column?: string, options?: object): Promise<number>;
fetchAll(options?: object): Promise<Collection>;
fetchPage(options?: object): Promise<{models: Collection, pagination: object}>;
where(...args: any[]): Model;
query(...args: any[]): Model;
}
// Instance methods
interface ModelInstance {
fetch(options?: {withRelated?: string[], require?: boolean, transacting?: Transaction, columns?: string[], debug?: boolean}): Promise<Model>;
save(key?: string | object, value?: any, options?: {method?: 'insert'|'update', patch?: boolean, transacting?: Transaction, debug?: boolean}): Promise<Model>;
destroy(options?: {require?: boolean, transacting?: Transaction, debug?: boolean}): Promise<Model>;
get(attribute: string): any;
set(key: string | object, value?: any, options?: {unset?: boolean, silent?: boolean}): Model;
has(attribute: string): boolean;
unset(attribute: string, options?: object): Model;
clear(options?: object): Model;
isNew(): boolean;
hasChanged(attribute?: string): boolean;
previous(attribute?: string): any;
previousAttributes(): object;
related(relationName: string): Model | Collection;
clone(): Model;
refresh(options?: {transacting?: Transaction}): Promise<Model>;
toJSON(options?: {shallow?: boolean, omitPivot?: boolean}): object;
}Array-like operations and bulk database operations for sets of models.
// Static methods
interface CollectionStatic {
forge(models?: any[], options?: object): Collection;
}
// Instance methods
interface CollectionInstance {
fetch(options?: {withRelated?: string[], require?: boolean, transacting?: Transaction, debug?: boolean}): Promise<Collection>;
fetchOne(options?: {require?: boolean, transacting?: Transaction}): Promise<Model>;
fetchPage(options?: {page?: number, pageSize?: number, limit?: number, offset?: number, withRelated?: string[]}): Promise<{models: Collection, pagination: object}>;
add(models: Model | Model[], options?: {at?: number, merge?: boolean, silent?: boolean}): Collection;
remove(models: Model | Model[], options?: {silent?: boolean}): Collection;
reset(models?: Model[], options?: {silent?: boolean}): Collection;
create(attributes: object, options?: {wait?: boolean, transacting?: Transaction}): Promise<Model>;
at(index: number): Model;
get(id: any): Model;
first(): Model;
last(): Model;
pluck(attribute: string): any[];
where(attributes: object): Model[];
clone(): Collection;
length: number;
models: Model[];
}Comprehensive relationship definitions including one-to-one, one-to-many, many-to-many, and polymorphic associations.
// Relation methods (available on Model instances)
interface RelationMethods {
hasOne(Target: string | ModelConstructor, foreignKey?: string, foreignKeyTarget?: string): Relation;
hasMany(Target: string | ModelConstructor, foreignKey?: string, foreignKeyTarget?: string): Relation;
belongsTo(Target: string | ModelConstructor, foreignKey?: string, foreignKeyTarget?: string): Relation;
belongsToMany(Target: string | ModelConstructor, tableName?: string, foreignKey?: string, otherKey?: string, foreignKeyTarget?: string, otherKeyTarget?: string): Relation;
morphOne(Target: string | ModelConstructor, name: string, columnNames?: string[], morphValue?: any): Relation;
morphMany(Target: string | ModelConstructor, name: string, columnNames?: string[], morphValue?: any): Relation;
morphTo(relationName: string, columnNames?: string[], ...targets: any[]): Relation;
through(Interim: string | ModelConstructor, throughForeignKey?: string, otherKey?: string, throughForeignKeyTarget?: string, otherKeyTarget?: string): Relation;
}Lifecycle events for models and collections with both synchronous and asynchronous event handling.
// Event methods (available on Models and Collections)
interface EventMethods {
on(events: string, callback: Function): this;
off(events?: string, callback?: Function): this;
trigger(event: string, ...args: any[]): this;
triggerThen(event: string, ...args: any[]): Promise<any>;
once(events: string, callback: Function): this;
}
// Model events: 'fetching', 'fetched', 'saving', 'creating', 'updating', 'saved', 'created', 'updated', 'destroying', 'destroyed'
// Collection events: 'fetched', 'add', 'remove', 'reset', 'sort', 'creating', 'attaching', 'attached', 'detaching', 'detached'Direct access to Knex query builder for complex database operations and custom queries.
// Query building methods (available on Models and Collections)
interface QueryMethods {
query(callback?: (qb: Knex.QueryBuilder) => void): this;
where(...args: any[]): this;
orderBy(column: string, direction?: string): this;
}Database transaction support with automatic rollback on errors and connection management.
/**
* Execute operations within a database transaction
* @param {Function} callback - Transaction callback function receiving transaction object
* @returns {Promise<any>} Promise resolving to callback result
*/
transaction(callback: (transaction: Transaction) => Promise<any>): Promise<any>;Extend Bookshelf functionality with community and custom plugins.
/**
* Load one or more plugins into this Bookshelf instance
* @param {string|Function|Array} plugin - Plugin name, function, or array of plugins
* @param {any} [options] - Plugin configuration options
* @returns {BookshelfInstance} This Bookshelf instance for chaining
*/
plugin(plugin: string | Function | Array, options?: any): BookshelfInstance;Bookshelf provides specific error classes for common database operation failures.
// Available error classes (accessible via bookshelf instance)
interface ErrorTypes {
NotFoundError: ErrorConstructor; // Model/Collection not found with require: true
EmptyError: ErrorConstructor; // Collection is empty when required
NoRowsUpdatedError: ErrorConstructor; // Update operation affected no rows
NoRowsDeletedError: ErrorConstructor; // Delete operation affected no rows
ModelNotResolvedError: ErrorConstructor; // Model name cannot be resolved from registry
}
// Access errors from bookshelf instance
const {
NotFoundError,
EmptyError,
NoRowsUpdatedError,
NoRowsDeletedError,
ModelNotResolvedError
} = bookshelf;
// Also available on Model and Collection constructors
const { NotFoundError: ModelNotFound } = bookshelf.Model;
const { EmptyError: CollectionEmpty } = bookshelf.Collection;Usage Examples:
try {
// This will throw NotFoundError if no user found
const user = await new User({id: 999}).fetch({require: true});
} catch (error) {
if (error instanceof bookshelf.NotFoundError) {
console.log('User not found');
}
}
try {
// This will throw EmptyError if no users found
const users = await Users.forge().fetch({require: true});
} catch (error) {
if (error instanceof bookshelf.EmptyError) {
console.log('No users in collection');
}
}// Core type definitions
interface Transaction {
// Knex transaction object
commit(): Promise<void>;
rollback(): Promise<void>;
}
interface Relation {
where(...args: any[]): Relation;
query(callback: (qb: Knex.QueryBuilder) => void): Relation;
through(Interim: string | ModelConstructor, throughForeignKey?: string, otherKey?: string): Relation;
withPivot(...columns: string[]): Relation;
}
interface ModelConstructor {
new(attributes?: object, options?: object): Model;
forge(attributes?: object, options?: object): Model;
extend(protoProps?: object, staticProps?: object): ModelConstructor;
}
interface CollectionConstructor {
new(models?: Model[], options?: object): Collection;
forge(models?: Model[], options?: object): Collection;
extend(protoProps?: object, staticProps?: object): CollectionConstructor;
}