Fork me on GitHub

Bookshelf is a javascript ORM for Node.js, built on the Knex SQL query builder. Featuring both promise based and traditional callback interfaces, it follows the Model & Collection patterns seen in Backbone.js, providing 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 well with PostgreSQL, MySQL, and SQLite3.

The project is hosted on GitHub, and has a comprehensive test suite.

Latest Release: 0.8.1 - Change Log

Current Develop — Travis Badge

Bookshelf is available for use under the MIT software license.

You can report bugs and discuss features on the GitHub issues page, add pages to the wiki or send tweets to @tgriesser.

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 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, provides flexible and 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.

Bookshelf follows the excellent foundation provided by Backbone.js Models and Collections, using similar patterns, naming conventions, and philosophies to build a lightweight, easy to navigate ORM. If you know how to use Backbone, you probably already know how to use Bookshelf.

Installation

You'll need to install a copy of knex.js, and either mysql, pg, or sqlite3 from npm.

$ npm install knex --save
$ npm install bookshelf --save

# Then add one of the following:
$ npm install pg
$ npm install mysql
$ npm install mariasql
$ 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.


var knex = require('knex')({
  client: 'mysql',
  connection: {
    host     : '127.0.0.1',
    user     : 'your_database_user',
    password : 'your_database_password',
    database : 'myapp_test',
    charset  : 'utf8'
  }
});

var bookshelf = require('bookshelf')(knex);

var User = bookshelf.Model.extend({
  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 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 something like bookshelf.js
var knex = require('knex')(dbConfig);
module.exports require('bookshelf')(knex);

// elsewhere, to use the bookshelf client:
var bookshelf = require('./bookshelf');

var Post = bookshelf.Model.extend({
  // ...
});

Bookshelf Models

Models are simple objects representing individual database rows, specifying the tableName and any relations to other models. They can be extended with any domain-specific methods, which can handle components such as validations, computed properties, and access control.

extendbookshelf.Model.extend([protoProps], [classProperties])
To create a Model class of your own, you extend bookshelf.Model and provide instance properties, as well as optional classProperties to be attached directly to the constructor function.

extend correctly sets up the prototype chain, so subclasses created with extend can be further extended and subclassed as far as you like.


var checkit  = require('checkit');
var Promise  = require('bluebird');
var bcrypt   = Promise.promisifyAll(require('bcrypt'));

var Customer = bookshelf.Model.extend({

  initialize: function() {
    this.on('saving', this.validateSave);
  },

  validateSave: function() {
    return checkit(rules).run(this.attributes);
  },

  account: function() {
    return this.belongsTo(Account);
  },

}, {

  login: Promise.method(function(email, password) {
    if (!email || !password) throw new Error('Email and password are both required');
    return new this({email: email.toLowerCase().trim()}).fetch({require: true}).tap(function(customer) {
      return bcrypt.compareAsync(customer.get('password'), password);
    });
  })

});

Customer.login(email, password)
  .then(function(customer) {
    res.json(customer.omit('password'));
  }).catch(Customer.NotFoundError, function() {
    res.json(400, {error: email + ' not found'});
  }).catch(function(err) {
    console.error(err);
  });

Brief aside on super: JavaScript does not provide a simple way to call super — the function of the same name defined higher on the prototype chain. If you override a core function like set, or save, and you want to invoke the parent object's implementation, you'll have to explicitly call it, along these lines:


var Customer = bookshelf.Model.extend({
  set: function() {
    ...
    bookshelf.Model.prototype.set.apply(this, arguments);
    ...
  }
});

forgebookshelf.Model.forge([attributes], [options])
A simple helper function to instantiate a new Model without needing new.


var Customer = bookshelf.Model.extend({
  tableName: 'customers'
});

Customer.forge({item: 'value'}).save().then(function() {
  // ...
});

collectionModel.collection([models], [options])
A simple static helper to instantiate a new Collection, setting the current model as the collection's target.


Customer.collection().fetch().then(function(collection) {
  // ...
})

constructor / initializenew Model([attributes], [options])
When creating an instance of a model, you can pass in the initial values of the attributes, which will be set on the model. If you define an initialize function, it will be invoked when the model is created.


new Book({
  title: "One Thousand and One Nights",
  author: "Scheherazade"
});

In rare cases, if you're looking to get fancy, you may want to override constructor, which allows you to replace the actual constructor function for your model.


var Books = bookshelf.Model.extend({

  tableName: 'documents',

  constructor: function() {
    bookshelf.Model.apply(this, arguments);
    this.on('saving', function(model, attrs, options) {
      options.query.where('type', '=', 'book');
    });
  }

});

The tableName and hasTimestamps properties will be directly attached if passed in the options during model creation.

If {parse: true} is passed as an option, the attributes will first be converted by parse before being set on the model.

tableNamemodel.tableName
A required property for any database usage, The tableName property refers to the database table name the model will query against.


var Television = bookshelf.Model.extend({
  tableName: 'televisions'
});

idAttributemodel.idAttribute
This tells the model which attribute to expect as the unique identifier for each database row (typically an auto-incrementing primary key named id). Note that if you are using parse and format (to have your model's attributes in camelCase, but your database's columns in snake_case, for example) this refers to the name returned by parse (myId), not the database column (my_id). Specify compound primary keys as an array of columns.

idmodel.id
A special property of models, the id is the unique identifier associated, named by the idAttribute. If you set the id in the attributes hash, it will be copied onto the model as a direct property. Models can be retrieved by id from collections, and the id is used in fetching models and building model relations.

setmodel.set(attributes, [options])
Set a hash of attributes (one or many) on the model. If any of the attributes change the model's state, a "change" event will be triggered. You may also pass individual keys and values.


customer.set({first_name: "Joe", last_name: "Customer"});

customer.set("telephone", "555-555-1212");

getmodel.get(attribute)
Get the current value of an attribute from the model. For example: note.get("title")

fetchmodel.fetch([options]).then(function(model) {...
Fetches a model from the database, using any attributes currently set on the model to form a select query. Returns a promise, which will resolve with the fetched model, or undefined if the model isn't fetched. If you wish to trigger an error if the fetched model is not found, pass {require: true} as one of the options to the fetch call. A "fetching" event will be fired just before the record is fetched; a good place to hook into for validation. A "fetched" event will be fired when a record is successfully retrieved. If you need to constrain the query performed by fetch, you can call the query method before calling fetch.


// select * from `books` where `ISBN-13` = '9780440180296'
new Book({'ISBN-13': '9780440180296'})
  .fetch()
  .then(function(model) {
    // outputs 'Slaughterhouse Five'
    console.log(model.get('title'));
  });

If you'd like to only fetch specific columns, you may specify a columns property, in the options for the fetch call, or use the query method, tapping into the knex column method to specify which columns will be fetched.

The withRelated parameter may be specified to fetch the resource, along with any specified relations named on the model. A single property, or an array of properties can be specified as a value for the withRelated property. The results of these relation queries will be loaded into a relations property on the model, may be retrieved with the related method, and will be serialized as properties on a toJSON call unless {shallow: true} is passed.


var Book = bookshelf.Model.extend({
  tableName: 'books',
  editions: function() {
    return this.hasMany(Edition);
  },
  genre: function() {
    return this.belongsTo(Genre);
  }
})

new Book({'ISBN-13': '9780440180296'}).fetch({
  withRelated: ['genre', 'editions']
}).then(function(book) {
  console.log(book.related('genre').toJSON());
  console.log(book.related('editions').toJSON());
  console.log(book.toJSON());
});

fetchAllModel.fetchAll / model.fetchAll([options]).then(function(collection) {...
Fetches a collection of models from the database, using any query parameters currently set on the model to form a select query. Returns a promise, which will resolve with the fetched collection. If you wish to trigger an error if no models are found, pass {require: true} as one of the options to the fetchAll call. A "fetching" event will be fired just before the collection is fetched; a good place to hook into for validations. A "fetched" event will be fired when a record is successfully retrieved. If you need to constrain the query performed by fetch, you can call the query method before calling fetch.

loadmodel.load(relations, [options]).then(function(model) {...
The load method takes an array of relations to eager load attributes onto a Model, in a similar way that the withRelated property works on fetch. Dot separated attributes may be used to specify deep eager loading.


new Posts().fetch().then(function(collection) {
  collection.at(0)
  .load(['author', 'content', 'comments.tags'])
  .then(function(model) {
    JSON.stringify(model);
  });
});

{
  title: 'post title',
  author: {...},
  content: {...},
  comments: [
    {tags: [...]}, {tags: [...]}
  ]
}

Relation Types:

Relationships are used to create associations between models.

There are four types of relationships that may be defined between Models and Collections: hasOne, hasMany, belongsTo, belongsToMany. The relations are specified by creating named methods on the model, which return the appropriate relation type.


var Summary = bookshelf.Model.extend({tableName: 'summaries'});

var Author = bookshelf.Model.extend({tableName: 'authors'});

var Owner  = bookshelf.Model.extend({tableName: 'owners'});

var Pages  = bookshelf.Model.extend({tableName: 'pages'});

var Book = bookshelf.Model.extend({
  summary: function() {
    return this.hasOne(Summary);
  },
  owner: function() {
    return this.belongsTo(Owner);
  },
  pages: function() {
    return this.hasMany(Pages);
  },
  author: function() {
    return this.belongsToMany(Author);
  }
});

new Book({id: 1}).related('summary').fetch().then(function(summary) {
  console.log(summary.toJSON());
});

// or:

new Book({id: 1}).summary().fetch().then(function(summary) {
  console.log(summary.toJSON());
});

Relations may also be loaded eagerly, by specifying a withRelated option during the fetch call. Note that the call to related will successfully return an object regardless of whether or not such a relation exists. To check for the existence of a related object, use the id.


new Book({id: 2}).fetch({
  withRelated: ['summary', 'owner', 'pages', 'author']
}).then(function(book) {
  console.log(book.toJSON());
  var owner = book.related('owner');
  if (owner.id) {
    console.log(owner.toJSON());
  }
});

You may also want to eagerly load related models on a model or collection after it has already been fetched. For this, you may use the load method to specify which relations should be eagerly loaded on the model or collection.


var accounts = new Accounts();
accounts.fetch()
  .then(function(collection) {
    return collection.at(0).load('account_info');
  })
  .then(function(model) {
    res.json({
      accounts: accounts.toJSON({shallow: true}),
      current_account: model
    });
  });

Nested eager loads may be performed, by separating the nested relations with '.'.


Story.where({id: 2}).fetch({
  withRelated: ['comments.tags', 'comments.author', 'author']
}).then(function(model) {
  JSON.stringify(model);
});

// performs 5 queries in total, outputting:
{
  id: 2,
  title: '',
  author: {...},
  comments: [
    {tags: [{...}, {...}], author: {...}},
    {tags: [...], author: {...}},
    {tags: [...], author: {...}}
  ]
}

An object may be passed as a relation, where the first argument is the key and the second a function which constraints the relation, called with the context of the current related model and accepting the Knex query builder object as the first argument.


Story.where({id: 2}).fetch({
  withRelated: ['comments.tags', 'comments.author', {
    'author': function(qb) {
      qb.where('status', 'active')
    }
  }]
}).then(...

hasOnemodel.hasOne(Target, [foreignKey])
A one-to-one relation is a very basic relation, where the model has exactly one of another Target model, referenced by a foreignKey in the Target model. By default, the foreignKey is assumed to be the singular form of the current model's tableName, followed by _id / _{{idAttribute}}.


var Record = bookshelf.Model.extend({
  tableName: 'health_records'
});

var Patient = bookshelf.Model.extend({
  tableName: 'patients',
  record: function() {
    return this.hasOne(Record);
  }
});

// select * from `health_records` where `patient_id` = 1;
new Patient({id: 1}).related('record').fetch().then(function(model) {
  ...
});

// alternatively, if you don't need the relation loaded on the patient's relations hash:
new Patient({id: 1}).record().fetch().then(function(model) {
  ...
});

If the foreign key is different than the one assumed by the query builder, you may specify it in the second argument of the query.

hasManymodel.hasMany(Target, [foreignKey])
Typically the most common relationship type, a hasMany is when the model has a "one-to-many" relationship with the Target model or collection. The model is referenced by a foreignKey in the Target model, which defaults to the singular form of the current model's tableName, followed by _id / _{{idAttribute}}.

belongsTomodel.belongsTo(Target, [foreignKey])
The belongsTo relationship is used when a model is a member of another Target model. It can be used in a one-to-one associations as the inverse of a hasOne. It can also used in one-to-many associations as the inverse of a hasMany (and is the one side of that association). In both cases, the belongsTo relationship is used for a model that is a member of another Target model, referenced by the foreignKey in the current model. By default, the foreignKey is assumed to be the singular form of the Target model's tableName, followed by _id / _{{idAttribute}}.


var Book = bookshelf.Model.extend({
  tableName: 'books',
  author: function() {
    return this.belongsTo(Author);
  }
});

// select * from `books` where id = 1
// select * from `authors` where id = book.author_id
Book.where({id: 1}).fetch({withRelated: ['author']}).then(function(book) {
  console.log(JSON.stringify(book.related('author')));
});

belongsToManymodel.belongsToMany(Target, [table], [foreignKey], [otherKey])
The belongsToMany method defines a many-to-many relation, where the current model is joined to one or more of a Target model through another table. The default name for the joining table is the two table names, joined by an underscore, ordered alphabetically. For example, a users table and an accounts table would have a joining table of accounts_users.


var Account = bookshelf.Model.extend({
  tableName: 'accounts'
});

var User = bookshelf.Model.extend({

  tableName: 'users',

  allAccounts: function () {
    return this.belongsToMany(Account);
  },

  adminAccounts: function() {
    return this.belongsToMany(Account).query({where: {access: 'admin'}});
  },

  viewAccounts: function() {
    return this.belongsToMany(Account).query({where: {access: 'readonly'}});
  }

});

The default key names in the joining table are the singular versions of the model table names, followed by _id / _{{idAttribute}}. So in the above case, the columns in the joining table would be user_id, account_id, and access, which is used as an example of how dynamic relations can be formed using different contexts. To customize the keys used in, or the tableName used for the join table, you may specify them like so:


this.belongsToMany(Account, 'users_accounts', 'userid', 'accountid');

If you wish to create a belongsToMany association where the joining table has a primary key, and more information about the model, you may create a belongsToMany through relation:


var Doctor = bookshelf.Model.extend({

  patients: function() {
    return this.belongsToMany(Patient).through(Appointment);
  }

});

var Appointment = bookshelf.Model.extend({

  patient: function() {
    return this.belongsTo(Patient);
  },

  doctor: function() {
    return this.belongsTo(Doctor);
  }

});

var Patient = bookshelf.Model.extend({

  doctors: function() {
    return this.belongsToMany(Doctor).through(Appointment);
  }

});

through.through(JoinModel, [throughFk], [otherKey])
The through method helps to create dynamic relations between models & collections, where a hasOne, hasMany, belongsTo, or belongsToMany relation may run through a JoinModel.

A good example of where this would be useful is if a book hasMany paragraphs through chapters. Consider the following examples:


var Book = bookshelf.Model.extend({

  tableName: 'books',

  // Find all paragraphs associated with this book, by
  // passing through the "Chapter" model.
  paragraphs: function() {
    return this.hasMany(Paragraph).through(Chapter);
  },

  chapters: function() {
    return this.hasMany(Chapter);
  }

});

var Chapter = bookshelf.Model.extend({

  tableName: 'chapters',

  paragraphs: function() {
    return this.hasMany(Paragraph);
  }

});

var Paragraph = bookshelf.Model.extend({

  tableName: 'paragraphs',

  chapter: function() {
    return this.belongsTo(Chapter);
  },

  // A reverse relation, where we can get the book from the chapter.
  book: function() {
    return this.belongsTo(Book).through(Chapter);
  }

});

The "through" table creates a pivot model, which it assigns to model.pivot after it is created. On toJSON, the pivot model is flattened to values prefixed with _pivot_.

attachrelation.attach(ids, [options])
Attaches one or more ids from a foreign table to the current table, in a belongsToMany relationship. Creates & saves a new model and attaches the model with the related model.


new Site({id: 1}).related('admins').attach([1, 2]);

The attach function may also take one or more models to attach to the current model:


var admin1 = new Admin({username: 'user1', password: 'test'});
var admin2 = new Admin({username: 'user2', password: 'test'});

Promise.all([admin1.save(), admin2.save()])
  .then(function() {
    return Promise.all([
      new Site({id: 1}).admins().attach([admin1, admin2]),
      new Site({id: 2}).admins().attach(admin2)
    ]);
  })

detachrelation.detach(ids, [options])
Detach one or more related objects from their pivot tables. If a model or id is passed, it attempts to remove the pivot table based on that foreign key. If no parameters are specified, we assume we will detach all related associations.

withPivotrelation.withPivot(columns)
The withPivot method is used exclusively on belongsToMany relations, and allows for additional fields to be pulled from the joining table.


var Tag = bookshelf.Model.extend({
  comments: function() {
    return this.belongsToMany(Comment).withPivot(['created_at', 'order']);
  }
});

updatePivotrelation.updatePivot(attrs, [options])
The updatePivot method is used exclusively on belongsToMany relations, and allows for updating pivot rows on the joining table. If you wish to confine the update clause, you may pass a query property on the options, which acts similar to the model.query method. If you are expecting an update and wish for an error (rejected promise) when no rows are updated, you can pass {require: true} in the options.

Polymorphic Associations:

With polymorphic associations, a model can belong to more than one other model, on a single association. For example, you might have a photo model that belongs to either a Site model or a Post model. Here’s how this could be declared:


var Site = bookshelf.Model.extend({
  tableName: 'sites',
  photo: function() {
    return this.morphOne(Photo, 'imageable');
  }
});

var Post = bookshelf.Model.extend({
  tableName: 'posts',
  photos: function() {
    return this.morphMany(Photo, 'imageable');
  }
});

var Photo = bookshelf.Model.extend({
  tableName: 'photos',
  imageable: function() {
    return this.morphTo('imageable', Site, Post);
  }
});

Optionally, if you wish to use column names other than the name suffixed with _type and _id (for example, if you use a different naming convention in your database), you may specify custom columnNames. This argument, when specified, expects an array containing the substitute _type and _id columns, respectively.

Note that any custom columnNames must be specified on both ends of the relationship! Examples are provided for each of the polymorphic relationship types individually.

morphOnemodel.morphOne(Target, name, [columnNames], [morphValue])
The morphOne is used to signify a polymorphic relation with another Target model, where the name of the model is used to determine which database table keys are used. The naming convention requires the name prefix an _id and _type field in the database. So for the case above the table names would be imageable_type and imageable_id. The morphValue may be optionally set to store/retrieve a different value in the _type column than the tableName.


var Site = bookshelf.Model.extend({
  tableName: 'sites',
  photo: function() {
    return this.morphOne(Photo, 'imageable');
  }
});

And with custom columnNames:


var Site = bookshelf.Model.extend({
  tableName: 'sites',
  photo: function() {
    return this.morphOne(Photo, 'imageable', ["ImageableType", "ImageableId"]);
  }
});

Note that both columnNames and morphValue are optional arguments. How your argument is treated when only one is specified, depends on the type. If your argument is an array, it will be assumed to contain custom columnNames. If it's not, it will be assumed to indicate a morphValue.

morphManymodel.morphMany(Target, name, [columnNames], [morphValue])
The morphMany property is essentially the same as a morphOne, but creating a collection rather than a model (similar to a hasOne vs. hasMany relation). The morphValue may be optionally set to store/retrieve a different value in the _type column than the tableName.


var Post = bookshelf.Model.extend({
  tableName: 'posts',
  photos: function() {
    return this.morphMany(Photo, 'imageable');
  }
});

And with custom columnNames:


var Post = bookshelf.Model.extend({
  tableName: 'posts',
  photos: function() {
    return this.morphMany(Photo, 'imageable', ["ImageableType", "ImageableId"]);
  }
});

The same argument parsing rules apply as for morphOne.

morphTomodel.morphTo(name, [columnNames], targets*)
The morphTo relation is used to specify the inverse of the "morphOne" or "morphMany" relations, where the targets (constructors) must be passed to signify which models are the potential opposite end of the polymorphic relation.


var Photo = bookshelf.Model.extend({
  tableName: 'photos',
  imageable: function() {
    return this.morphTo('imageable', Site, Post);
  }
});

And with custom columnNames:


var Photo = bookshelf.Model.extend({
  tableName: 'photos',
  imageable: function() {
    return this.morphTo('imageable', ["ImageableType", "ImageableId"], Site, Post);
  }
});

relationsmodel.relations
The relations property is the internal hash containing each of the relations (models or collections) loaded on the model with load or with the withRelated property during fetch. This is also populated by default when using Model#related to retrieve a relation, if one does not already exist. You can access the relation using the related method on the model.


new Photo({id: 1}).fetch({
  withRelated: ['account']
}).then(function(photo) {
  if (photo) {
    var account = photo.related('account');
    if (account.id) {
      return account.related('trips').fetch();
    }
  }
});

relatedDatamodel.relatedData
The relatedData is an internal "Relation" object of information about the current model's relation, including the type, foreignKey, fkValue, and other data depending on the relation type. Mainly for use internally, but possibly helpful for custom behavior.

savemodel.save([params], [options]).then(function(model) {...
The Model's save method is used to perform an insert or update query with the model, dependent on whether the model isNew. If the model isNew, any defaults will be added to the object being saved. If you wish to use update rather than insert or vice versa, the {method: ...} option may be specified to explicitly state which bookshelf.Sync method to use in the save call. If the method is update, a {defaults: true} option may be specified to also merge with the default values.


new Post({name: 'New Article'}).save().then(function(model) {
  // ...
});

If you only wish to update with the params passed to the save, you may pass a {patch: true} flag to the database:


// update authors set "bio" = 'Short user bio' where "id" = 1
new Author({id: 1, first_name: 'User'})
  .save({bio: 'Short user bio'}, {patch: true})
  .then(function(model) {
    // ...
  });

Several events fired on the model when saving: a "creating", or "updating" event if the model is being inserted or updated, and a "saving" event in either case. To prevent saving the model (with validation, etc.), throwing an error inside one of these event listeners will stop saving the model and reject the promise. A "created", or "updated" event is fired after the model is saved, as well as a "saved" event either way. If you wish to modify the query when the "saving" event is fired, the knex query object should be available on options.query.

destroymodel.destroy
The Model's destroy method performs a delete on the model, using the model's idAttribute to constrain the query. A "destroying" event is triggered on the model before being destroyed. To prevent destroying the model (with validation, etc.), throwing an error inside one of these event listeners will stop destroying the model and reject the promise. A "destroyed" event is fired after the model's removal is completed.

attributesmodel.attributes
The attributes property is the internal hash containing the database row.

Please use set to update the attributes instead of modifying them directly. If you'd like to retrieve and munge a copy of the model's attributes, use toJSON instead.

defaultsmodel.defaults or model.defaults()
The defaults hash (or function) can be used to specify the default attributes for your model. Unlike with Backbone's model defaults, Bookshelf defaults are applied before an "insert" query, rather than during object creation.


var Meal = bookshelf.Model.extend({
  tableName: 'meals',
  defaults: {
    appetizer:  "caesar salad",
    entree:     "ravioli",
    dessert:    "cheesecake"
  }
});

new Meal().save().then(function(meal) {
  alert("Dessert will be " + meal.get('dessert'));
});

Remember that in JavaScript, objects are passed by reference, so if you include an object as a default value, it will be shared among all instances. Instead, define defaults as a function.

formatmodel.format(attributes, [options])
The format method is used to modify the current state of the model before it is persisted to the database. The attributes passed are a shallow clone of the model, and are only used for inserting/updating - the current values of the model are left intact.


// Example of a "format" to convert camelCase to snake_case when saving, using `underscore.string`
model.format = function(attrs) {
  return _.reduce(attrs, function(memo, val, key) {
    memo[_.str.underscored(key)] = val;
    return memo;
  }, {});
};

parsemodel.parse(response, [options])
The parse method is called whenever a model's data is returned in a fetch call. The function is passed the raw database response object, and should return the attributes hash to be set on the model. The default implementation is a no-op, simply passing through the JSON response. Override this if you need to format the database responses - for example calling JSON.parse on a text field containing JSON, or explicitly typecasting a boolean in a sqlite3 database response.


// Example of a "parse" to convert snake_case to camelCase, using `underscore.string`
model.parse = function(attrs) {
  return _.reduce(attrs, function(memo, val, key) {
    memo[_.str.camelize(key)] = val;
    return memo;
  }, {});
};

toJSONmodel.toJSON([options])
Return a copy of the model's attributes for JSON stringification. If the model has any relations defined, this will also call toJSON on each of the related objects, and include them on the object unless {shallow: true} is passed as an option. For more information on the use of toJSON, check out the JavaScript API for JSON.stringify.


var artist = new bookshelf.Model({
  firstName: "Wassily",
  lastName: "Kandinsky"
});

artist.set({birthday: "December 16, 1866"});

console.log(JSON.stringify(artist));
// {firstName: "Wassily", lastName: "Kandinsky", birthday: "December 16, 1866"}

Additional Backbone Methods (4)
Bookshelf also proxies to Backbone.js for more core Model methods that aren't documented here. The Backbone documentation applies equally for Bookshelf, and can be used to read more on these methods.

Underscore Methods (6)
Bookshelf proxies to the Underscore implementation to provide 6 object functions on bookshelf.Model. They aren't all documented here, but you can take a look at the Underscore documentation for the full details…


user.pick('first_name', 'last_name', 'email');

chapters.keys().join(', ');

whereModel.where / model.where([parameters])
The where method is used as convenience for the most common query method, adding a where clause to the builder. Any additional knex methods may be accessed using the query method.


model
  .where({other_id: 5})
  .fetch()
  .then(function(model) {
    ...
  });

queryModel.query / model.query([method], [*parameters])
The query method is used to tap into the underlying Knex query builder instance for the current model. If called with no arguments, it will return the query builder directly. Otherwise, it will call the specified method on the query builder, applying any additional arguments from the model.query call. If the method argument is a function, it will be called with the Knex query builder as the context and the first argument, returning the current model.


model
  .query('where', 'other_id', '=', '5')
  .fetch()
  .then(function(model) {
    ...
  });

model
  .query({where: {other_id: '5'}, orWhere: {key: 'value'}})
  .fetch()
  .then(function(model) {
    ...
  });

model.query(function(qb) {
  qb.where('other_person', 'LIKE', '%Demo').orWhere('other_id', '>', 10);
}).fetch()
  .then(function(model) {...

var qb = model.query();
    qb.where({id: 1}).select().then(function(resp) {...

resetQuerymodel.resetQuery()
Used to reset the internal state of the current query builder instance. This method is called internally each time a database action is completed by bookshelf.Sync.

hasTimestampsmodel.hasTimestamps
If this value is set, the timestamp method will be called on a model.save() to set the created_at and updated_at values on save. If this value is an array, the first value will be used as the model's key for the "created at" value and the second for the "updated at" value.


var PostModel = bookshelf.Model.extend({
  // If, for example, you snakify in your `format` function (from model to db)
  // and camelize in `parse` (from db to model), then you would camelize your model like so:
  hasTimestamps: ['createdAt', 'updatedAt']
});

timestampmodel.timestamp(options)
Sets the timestamp attributes on the model, if hasTimestamps is set to true or an array. The default implementation is to check if the model isNew or if {method: 'insert'} is provided as an option and set the created_at and updated_at attributes to the current date if it is being inserted, and just the updated_at attribute if it's being updated. You may override this method if you wish to use different column names or types for the timestamps.

clonemodel.clone()
Returns a new instance of the model with identical attributes, including any relations from the cloned model.

isNewmodel.isNew()
Checks for the existence of an id to determine whether the model is considered "new".


var modelA = new bookshelf.Model();
modelA.isNew(); // true

var modelB = new bookshelf.Model({id: 1});
modelB.isNew(); // false

changedmodel.changed
The changed property is the internal hash containing all the attributes that have changed since the model's last fetch, save, or destroy.

hasChangedmodel.hasChanged([attribute])
Returns true if any attribute has changed since the last fetch, save, or destroy. If an attribute is passed, returns true only if that specific attribute has changed.

previousmodel.previous(attribute)
Returns the this previous value of a changed attribute, or undefined if one had not been specified previously.

previousAttributesmodel.previousAttributes()
Return a copy of the model's previous attributes from the model's last fetch, save, or destroy. Useful for getting a diff between versions of a model, or getting back to a valid state after an error occurs.

syncmodel.sync(method, model, [options])
Creates and returns a new Bookshelf.Sync instance. Can be overridden for custom behavior.

Associations

Bookshelf handles one-to-one, one-to-many, and many-to-many associations by defining relationships on models.

One-to-one

One-to-one associations can be created with belongsTo, hasOne, and morphOne relation types.


var Book, Summary;

Book = bookshelf.Model.extend({
  tableName: 'books',
  summary: function() {
    return this.hasOne(Summary);
  }
});

Summary = bookshelf.Model.extend({
  tableName: 'summaries',
  book: function() {
    return this.belongsTo(Book);
  }
});

A Knex migration for the above relationship could be created with:


exports.up = function(knex, Promise) {
  return knex.schema.createTable('books', function(table) {
    table.increments('id').primary();
    table.string('name');
  }).createTable('summaries', function(table) {
    table.increments('id').primary();
    table.string('details');
    table.integer('book_id').unique().references('books.id');
  });
};

exports.down = function(knex, Promise) {
  return knex.schema.dropTable('books')
    .dropTable('summaries');
};

One-to-many

One-to-many associations can be created with belongsTo, hasMany, morphMany / morphTo, and some of the through relation types.


var Book = bookshelf.Model.extend({
  tableName: 'books',
  pages: function() {
    return this.hasMany(Page);
  }
});

var Page = bookshelf.Model.extend({
  tableName: 'pages',
  book: function() {
    return this.belongsTo(Book);
  }
});

A Knex migration for the above relationship could be created with:


exports.up = function(knex, Promise) {
  return knex.schema.createTable('books', function(table) {
    table.increments('id').primary();
    table.string('name');
  }).createTable('pages', function(table) {
    table.increments('id').primary();
    table.string('content');
    table.integer('book_id').references('books.id')
  });
};

exports.down = function(knex, Promise) {
  return knex.schema.dropTable('books')
    .dropTable('pages');
};

Many-to-many

Many-to-many associations can be created with belongsToMany, and through relation types.


var Book = bookshelf.Model.extend({
  tableName: 'books',
  authors: function() {
    return this.belongsToMany(Author);
  }
});

var Author = bookshelf.Model.extend({
  tableName: 'authors',
  books: function() {
    return this.belongsToMany(Book);
  }
});

A Knex migration for the above relationship could be created with:


exports.up = function(knex, Promise) {
  return knex.schema.createTable('books', function(table) {
    table.increments('id').primary();
    table.string('name');
  }).createTable('authors', function(table) {
    table.increments('id').primary();
    table.string('name');
  }).createTable('authors_books', function(table) {
    table.integer('author_id').references('authors.id');
    table.integer('book_id').references('books.id');
  });
};

exports.down = function(knex, Promise) {
  return knex.schema.dropTable('books')
    .dropTable('authors')
    .dropTable('authors_books');
};

Bookshelf Collections

Collections are ordered sets of models returned from the database, from a fetchAll call. They may be used with a full suite of Underscore methods.

extendbookshelf.Collection.extend(properties, [classProperties])
To create a Collection class of your own, extend Bookshelf.Collection, providing instance properties, as well as optional classProperties to be attached directly to the collection's constructor function.

forgebookshelf.Collection.forge([models], [options])
A simple helper function to instantiate a new Collection without needing new.


var Promise = require('bluebird');
var Accounts = bookshelf.Collection.extend({
  model: Account
});

var accounts = Accounts.forge([
  {name: 'Person1'},
  {name: 'Person2'}
])

Promise.all(accounts.invoke('save')).then(function() {
  // collection models should now be saved...
});

modelcollection.model
Override this property to specify the model class that the collection contains. If defined, you can pass raw attributes objects (and arrays) to add, create, and reset, and the attributes will be converted into a model of the proper type. Unlike in Backbone.js, the model attribute may not be a polymorphic model.


var Trilogy = bookshelf.Collection.extend({
  model: Book
});

constructor / initializenew Collection([models], [options])
When creating a Collection, you may choose to pass in the initial array of models. The collection's comparator may be included as an option. Passing false as the comparator option will prevent sorting. If you define an initialize function, it will be invoked when the collection is created.


var tabs = new TabSet([tab1, tab2, tab3]);

modelscollection.models
Raw access to the JavaScript array of models inside of the collection. Usually you'll want to use get, at, or the Underscore methods to access model objects, but occasionally a direct reference to the array is desired.

parsecollection.parse(response, [options])
The parse method is called whenever a collection's data is returned in a fetch call. The function is passed the raw database response array, and should return an array to be set on the collection. The default implementation is a no-op, simply passing through the JSON response.

toJSONcollection.toJSON([options])
Return an array containing the toJSON for each model in the collection. For more information about toJSON, check out JavaScript's JSON API. You may pass {shallow: true} in the options to omit any relations loaded on the collection's models.

fetchcollection.fetch([options])
Fetch the default set of models for this collection from the database, resetting the collection when they arrive. If you wish to trigger an error if the fetched collection is empty, pass {require: true} as one of the options to the fetch call. A "fetched" event will be fired when records are successfully retrieved. If you need to constrain the query performed by fetch, you can call the query method before calling fetch.

If you'd like to only fetch specific columns, you may specify a columns property, in the options for the fetch call, or use the query method, tapping into the knex column method to specify which columns will be fetched.

The withRelated parameter may be specified to fetch the models of the collection, eager loading any specified relations named on the model. A single property, or an array of properties can be specified as a value for the withRelated property. The results of these relation queries will be loaded into a relations property on the respective models, may be retrieved with the related method, and will be serialized as properties on a toJSON call unless {shallow: true} is passed.

fetchOnecollection.fetchOne([options])
Fetch and return a single model from the collection, maintaining any relation data from the collection, and any query parameters that have already been passed to the collection. Especially helpful on relations, where you would only like to return a single model from the associated collection.


// select * from authors where site_id = 1 and id = 2 limit 1;
new Site({id:1})
  .authors()
  .query({where: {id: 2}})
  .fetchOne()
  .then(function(model) {
    ... //
  });

mapThencollection.mapThen(iterator, [context])
Shortcut for calling Promise.map(collection.models, iterator) with an optionally bound context, returning a promise which is resolved with the result of the map.


new Collection([{id: 1}, {id: 2}, {id: 3}]).mapThen(function(model) {

  return model.save().then(function() {
    return model.get('id') + '-saved';
  });

}).then(function(resp) {

  // resp: ['1-saved', '2-saved', '3-saved']

});

reduceThencollection.reduceThen(iterator, [memo], [context])
Shortcut for calling Promise.reduce(collection.models, iterator, memo) with an optionally bound context, returning a promise which is resolved with the result of the reduce.


new Collection([{id: 1}, {id: 2}, {id: 3}]).reduceThen(function(memo, model) {

  return model.save().then(function() {
    memo++;
    return memo;
  });

}, 0).then(function(saved) {

  // resp: 3

});

invokeThencollection.invokeThen(method, args*)
Shortcut for calling Promise.all around a collection.invoke, this will delegate to the collection's invoke method, resolving the promise with an array of responses all async (and sync) behavior has settled. Useful for bulk saving or deleting models:


collection.invokeThen('save', null, options).then(function() {
  // ... all models in the collection have been saved
});

collection.invokeThen('destroy', options).then(function() {
  // ... all models in the collection have been destroyed
});

loadcollection.load(relations, options).then(function(collection) {...
The load method can be used to eager load attributes onto a Collection, in a similar way that the withRelated property works on fetch. Nested eager loads can be specified by separating the nested relations with '.'.


new Stories().fetch({
  withRelated: 'author'
}).tap(function(stories) {
  if (req.withComments) return stories.load(['comments.tags', 'comments.author']);
}).then(function(stories) {
  console.log(JSON.stringify(stories));
});

addcollection.add(models, [options])
Add a model (or an array of models) to the collection, You may also pass raw attributes objects, and have them be vivified as instances of the model. Pass {at: index} to splice the model into the collection at the specified index. If you're adding models to the collection that are already in the collection, they'll be ignored, unless you pass {merge: true}, in which case their attributes will be merged into the corresponding models, firing any appropriate "change" events.

var ships = new Backbone.Collection;

ships.on("add", function(ship) {
  alert("Ahoy " + ship.get("name") + "!");
});

ships.add([
  {name: "Flying Dutchman"},
  {name: "Black Pearl"}
]);

Note that adding the same model (a model with the same id) to a collection more than once
is a no-op.

removecollection.remove(models, [options])
Remove a model (or an array of models) from the collection, but does not remove the model from the database, use the model's destroy method for this.

resetcollection.reset([models], [options])
Adding and removing models one at a time is all well and good, but sometimes you have so many models to change that you'd rather just update the collection in bulk. Use reset to replace a collection with a new list of models (or attribute hashes). Calling collection.reset() without passing any models as arguments will empty the entire collection.

setcollection.set(models, [options])
The set method performs a "smart" update of the collection with the passed list of models. If a model in the list isn't yet in the collection it will be added; if the model is already in the collection its attributes will be merged; and if the collection contains any models that aren't present in the list, they'll be removed. If you'd like to customize the behavior, you can disable it with options: {add: false}, {remove: false}, or {merge: false}.


var vanHalen = new Backbone.Collection([eddie, alex, stone, roth]);

vanHalen.set([eddie, alex, stone, hagar]);

// Fires a "remove" event for roth, and an "add" event for "hagar".
// Updates any of stone, alex, and eddie's attributes that may have
// changed over the years.

getcollection.get(id)
Get a model from a collection, specified by an id, a cid, or by passing in a model.


var book = library.get(110);

atcollection.at(index)
Get a model from a collection, specified by index. Useful if your collection is sorted, and if your collection isn't sorted, at will still retrieve models in insertion order.

querycollection.query([method], [*parameters])
The query method is used to tap into the underlying Knex query builder instance for the current collection. If called with no arguments, it will return the query builder directly. Otherwise, it will call the specified method on the query builder, applying any additional arguments from the collection.query call. If the method argument is a function, it will be called with the Knex query builder as the context and the first argument, returning the current model.


var qb = collection.query();
    qb.where({id: 1}).select().then(function(resp) {...

collection.query(function(qb) {
  qb.where('id', '>', 5).andWhere('first_name', '=', 'Test');
}).fetch()
  .then(function(collection) {...

collection
  .query('where', 'other_id', '=', '5')
  .fetch()
  .then(function(collection) {
    ...
  });

resetQuerycollection.resetQuery()
Used to reset the internal state of the current query builder instance. This method is called internally each time a database action is completed by Bookshelf.Sync.

synccollection.sync([options])
Creates and returns a new Bookshelf.Sync instance. Can be overridden for custom behavior.

createcollection.create(relations, options).then(function(model) {...
Convenience to create a new instance of a model within a collection. Equivalent to instantiating a model with a hash of attributes, saving the model to the database, and adding the model to the collection after being successfully created. Returns a promise, resolving with the new model.

Additional Backbone Methods (12)
Bookshelf also proxies to Backbone.js for more core Collection methods that aren't documented here. The Backbone documentation applies equally for Bookshelf, and can be used to read more on these methods.

Underscore Methods (28)
Just as with Backbone, Bookshelf proxies to the Underscore implementation to provide 28 iteration functions on Bookshelf.Collection. They aren't all documented here, but you can take a look at the Underscore documentation for the full details…

Bookshelf.Events

Backbone Methods (5)
Bookshelf mixes in the Events module from Backbone, and therefore each Model and Collection has access to each of the following methods, which can be referenced in the Backbone documentation.

.triggerThen(name, args*)
A promise version of "trigger", returning a promise which resolves with all return values from triggered event handlers. If any of the event handlers throw an Error or return a rejected promise, the promise will be rejected. Used internally on the "creating", "updating", "saving", and "destroying" events, and can be helpful when needing async event handlers (for validations, etc).

Catalog of Events
Here's the complete list of recognized Bookshelf events, with arguments. Other events from Backbone might be triggered, but aren't officially supported unless they are noted in this list. You're also free to trigger your own events on Models and Collections and The Bookshelf object itself mixes in Events, and can be used to emit any global events that your application needs.

Model:

Collection:

For fetching events, columns is an array of columns that will be passed to knex. If you want to modify the columns, make sure to preserve the reference:


  // won't work
  function (model, columns) {
    columns = ['my', 'columns'];
  }

  // will work
  function (model, columns) {
    columns.length = 0;
    columns.push('my', 'columns');
  }
  

Utility

Bookshelf.knex
A reference to the Knex.js instance being used by Bookshelf.

bookshelf.transaction
An alias to Knex.transaction, the transaction object must be passed along in the options of any relevant Bookshelf.Sync calls, to ensure all queries are on the same connection. The entire transaction block is a promise that will resolve when the transaction is committed, or fail if the transaction is rolled back.


var Promise = require('bluebird');

Bookshelf.transaction(function(t) {
  return new Library({name: 'Old Books'})
    .save(null, {transacting: t})
    .tap(function(model) {
      return Promise.map([
        {title: 'Canterbury Tales'},
        {title: 'Moby Dick'},
        {title: 'Hamlet'}
      ], function(info) {

        // Some validation could take place here.
        return new Book(info).save({'shelf_id': model.id}, {transacting: t});
      });
    });
}).then(function(library) {
  console.log(library.related('books').pluck('title'));
}).catch(function(err) {
  console.error(err);
});

Plugins

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 or in the GitHub issue tracker.

F.A.Q.

Can I use standard node.js style callbacks.
Yes - you can call .asCallback(function(err, resp) { on any "sync" 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 you 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 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. 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} as one of the options in your initialize settings, you can see all of the query calls being made. Sometimes you need to dive a bit further into the various calls and see what all is going on behind the scenes. I'd recommend 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 these promises... but in short, adding:


process.stderr.on('data', function(data) {
  console.log(data);
});

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.

How do I run the test suite?
The test suite looks for an environment variable called BOOKSHELF_TEST for the path to the database configuration. If you run the following command: $ export BOOKSHELF_TEST='/path/to/your/bookshelf_config.js', replacing with the path to your config file, and the config file is valid, the test suite should run with npm test.

Also note that you will have to create the appropriate database(s) for the test suite to run. For example, with mysql, you'll need to run the command create database bookshelf_test; in addition to exporting the correct test settings prior to running the test suite.

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.

Change Log

0.8.1May 12, 2015Diff

0.8.0May 1, 2015Diff

Breaking changes:

0.7.9Oct 28, 2014Diff

0.7.8Oct 28, 2014Diff

0.7.7July 23, 2014Diff

0.7.6June 29, 2014Diff
Add omitPivot flag on toJSON options for omitting the _pivot_ keys in through and belongsToMany relations (#404).

0.7.5June 23, 2014Diff
Fix missing NotFoundError & EmptyError on Model & Collection, respectively (#389, 399).

0.7.4June 18, 2014Diff
Added bookshelf.model(name, protoProps, [staticProps]) syntax for registry plugin.

0.7.3June 17, 2014Diff
Fix for collection dropping models early in set, #376.

0.7.2June 12, 2014Diff
Pass a cloned copy of the model's attributes to format rather than the original, related to #315.

0.7.1June 10, 2014Diff
Ensure the knex version >= 0.6.10, where a major regression affecting column names was fixed.

0.7.0June 9, 2014

0.6.12June 5, 2014Diff
Fix for eager loaded belongsTo relation bug with custom parse/format (#377).

0.6.11June 4, 2014Diff
Temporarily add knex to peerDependencies until 0.7 is released to support knex 0.6 and there exists a better internal method of doing a semver check. Fix for belongsTo relation bug (#353).

0.6.10April 3, 2014Diff

0.6.9April 3, 2014Diff
Only prefix model fields with the "tableName" after format has been called, (#308).

0.6.8March 6, 2014Diff

Show Full Change log