Getting Started with Bookshelf.js

Share this article

In the last three years we have seen a spike in JavaScript’s popularity. Over the years, there have been multiple attempts at taking the popular language to the server. The most prevailing of these attempts has been Node.js, which was presented to the community as a quick way of writing server applications. The selling point for Node was speed, both in terms of performance and in development time. With this kind of popularity the community grew and the project benefited from more contributors, resulting in high quality modules like Express.js.

As a result people started building complete back ends using Node. One of the most important things a back end system should do is communicate with databases efficiently. This is where Object-Relational Mapping, or ORM, software comes in. Normally, developers need to be good in both the programming language they are using and SQL in order to communicate with databases. ORMs make life easier by allowing developers to interact with databases within the programming language of their choice using objects. This article introduces ORMs, and looks specifically at the Bookshelf.js ORM.

What is an ORM?

Wikipedia defines Object-Relational Mapping as:

a programming technique for converting data between incompatible type systems in object-oriented programming languages. This creates, in effect, a “virtual object database” that can be used from within the programming language

Now in our case, the programming language is JavaScript, and the incompatible system is a relational database system such as MySQL. This means that an ORM Library should allow us to communicate with the database the same way we interact with regular JavaScript objects. There are many ORM libraries that exist for Node.js, the popular ones being Persistence.js, Sequelize.js, and Bookshelf.js. This article will introduce Bookshelf.js.

Bookshelf.js Examples

Database interactions are typically centered around the four CRUD operations – create, read, update, and delete. Bookshelf.js provides an intuitive way of doing this, for example, this what a create operation would look like:

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

Assuming that Post is a model which has a corresponding database table and that name is an attribute which corresponds to a column in a database table.

Similarly, a read operation looks like this:

// select * from `user` where `email` = 'user@mail.com'
new User({email: 'user@mail.com'})
  .fetch()
  .then(function(model) {
    console.log(model.get('gender'));
  });

Notice the then call in the code. Bookshelf.js supports promise-based interfaces, which in this case means the anonymous function passed into then will only be called if your query was successful. The model is the resulting JavaScript object, which you can use to access attributes associated with User. In our case model.get('gender') returns the gender of our user.

Building an API with Bookshelf.js and Express.js

For a more complete example, suppose we were commissioned to build a JSON API for a blog with the following resources:

GET  /api/article
GET  /api/article/:article_id
POST /api/article

And, the client already has a MySQL database with the following table:

create table article (
  id int not null primary key,
  title varchar(100) null,
  body text null,
  author varchar(100) null
);

To begin, we need to setup our Express.js environment with package.json:

{
  "name": "article_api",
  "description": "expose articles via JSON",
  "version": "0.0.1",
  "private": true,
  "dependencies": {
    "bluebird": "^2.1.3",
    "body-parser": "^1.3.1",
    "express": "4.4.3",
    "mysql": "*",
    "knex": "*",
    "bookshelf": "*"
  }
}

We need knex query builder because bookshelf depends on it, and we need bluebird for promises. Our app.js structure now looks like this:

// When the app starts
var express = require('express');
var app = express();
var bodyParser = require('body-parser');
var Promise = require('bluebird');

var dbConfig = {
  client: 'mysql',
  connection: {
    host: 'localhost',
    user: 'root',
    password: 'your_password',
    database: 'blog',
    charset: 'utf8'
  }
};

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

app.set('bookshelf', bookshelf);

var allowCrossDomain = function(req, res, next) {
  res.header('Access-Control-Allow-Origin', '*');
  next();
};

app.use(allowCrossDomain);
// parse application/x-www-form-urlencoded
app.use(bodyParser.urlencoded());

// parse application/json
app.use(bodyParser.json());

// parse application/vnd.api+json as json
app.use(bodyParser.json({type: 'application/vnd.api+json'}));

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

// {our model definition code goes here}

app.listen(3000, function() {
  console.log('Express started at port 3000');
});

Our MySQL database is called blog. We need to define the article model and tie it to the article table. We will replace // {our model definition code goes here} with:

var Article = bookshelf.Model.extend({
  tableName: 'article'
});

Believe it or not, that’s all it takes to define a model in Bookshelf.js. We can now use this model to query the database in our API. To start with, the GET /api/article method should return all articles in the database:

app.get('/api/article', function(req, res) {
  new Article().fetchAll()
    .then(function(articles) {
      res.send(articles.toJSON());
    }).catch(function(error) {
      console.log(error);
      res.send('An error occured');
    });
});

fetchAll in Bookshelf.js fetches all entries in the database table, and catch is only executed if an error occurs (there are more model methods in the documentation).

Conclusion

Node.js has grown as a technology and can be used to build web applications and APIs through modules like Express.js. Bookshelf.js makes it easier to talk to a relational database with your Node.js application by wrapping the underlying database table structure and exposing regular JavaScript objects for querying. This article has provided a high level introduction. A complete implementation of the demo project is available on GitHub.

Frequently Asked Questions about Getting Started with Bookshelf.js

What is Bookshelf.js and why should I use it?

Bookshelf.js is a JavaScript library that provides a simple and elegant API for interacting with a SQL database. It supports transactions, eager/nested-eager relation loading, polymorphic associations, and much more. The main advantage of using Bookshelf.js is its simplicity and flexibility. It allows you to write less code while doing more, making your development process faster and more efficient.

How do I install Bookshelf.js?

To install Bookshelf.js, you need to have Node.js and npm installed on your system. Once you have these, you can install Bookshelf.js by running the command npm install bookshelf knex sqlite3 --save in your terminal. This will install Bookshelf.js along with Knex.js (a SQL query builder) and SQLite3 (a lightweight disk-based database).

How do I connect Bookshelf.js to my database?

To connect Bookshelf.js to your database, you first need to initialize Knex.js with your database configuration. Then, you pass this initialized Knex instance to Bookshelf.js. Here’s a basic example:

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

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

How do I define a model in Bookshelf.js?

In Bookshelf.js, a model is defined by extending the base Model provided by Bookshelf.js. Here’s an example of how to define a User model:

var User = bookshelf.model('User', {
tableName: 'users',
});

How do I fetch data from the database using Bookshelf.js?

Fetching data from the database is straightforward with Bookshelf.js. You can use the fetch method provided by the model. Here’s an example:

new User({id: 1})
.fetch()
.then(user => {
console.log(user.toJSON());
})
.catch(err => {
console.error(err);
});

How do I save data to the database using Bookshelf.js?

Saving data to the database is also simple with Bookshelf.js. You can use the save method provided by the model. Here’s an example:

new User({name: 'John Doe', email: 'john.doe@example.com'})
.save()
.then(user => {
console.log(user.toJSON());
})
.catch(err => {
console.error(err);
});

How do I update data in the database using Bookshelf.js?

Updating data in the database can be done using the save method as well. You just need to fetch the model first, then call save with the new data. Here’s an example:

new User({id: 1})
.fetch()
.then(user => {
return user.save({email: 'new.email@example.com'});
})
.then(user => {
console.log(user.toJSON());
})
.catch(err => {
console.error(err);
});

How do I delete data from the database using Bookshelf.js?

Deleting data from the database can be done using the destroy method provided by the model. Here’s an example:

new User({id: 1})
.destroy()
.then(user => {
console.log('User deleted');
})
.catch(err => {
console.error(err);
});

How do I handle relationships in Bookshelf.js?

Bookshelf.js provides several methods to handle relationships between models, such as hasOne, hasMany, belongsTo, and belongsToMany. Here’s an example of a User model that has many Post models:

var User = bookshelf.model('User', {
tableName: 'users',
posts: function() {
return this.hasMany('Post');
}
});

How do I handle transactions in Bookshelf.js?

Transactions in Bookshelf.js can be handled using the transaction method provided by Knex.js. Here’s an example:

bookshelf.transaction(function(t) {
return new User({name: 'John Doe', email: 'john.doe@example.com'})
.save(null, {transacting: t})
.then(function(user) {
return new Post({title: 'New post', content: 'Post content', user_id: user.id})
.save(null, {transacting: t});
});
})
.then(function() {
console.log('Transaction successful');
})
.catch(function(err) {
console.error(err);
});

Sibongakonke NkosiSibongakonke Nkosi
View Author

Sibongakonke is a creative mobile and web developer, start-up founder and technical trainer. When he is not coding or tinkering with the raspberry pi, he spends the rest of his time ice skating.

Bookshelf.jsColinIorm
Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week