Getting Started with Bookshelf.js

Sibongakonke Nkosi

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.

Free book: Jump Start HTML5 Basics

Grab a free copy of one our latest ebooks! Packed with hints and tips on HTML5's most powerful new features.

  • M S

    Is there protection from code-injection via in-data?
    If so, where is that done?
    In the bookshelf, or node, or express, or some other stuff between any of those?

    Documentation for node-stuff is mostly useless for casual visitors.

  • M S

    From what i can see there, there are functions for escaping input, some are experimental, and its up to the user to know to use them and how.

    So are the layers on top of that on actually doing this, or is it assumed that I’m doing it?

    Im asking, because from my experience, the people who love “frameworks” like these really likes to make happy-go-lucky announcements like:
    “Of course our stuff make its easy to disarm ANY bombs, ridiculously simple even. ANYONE can now do it!”

    …leaving out the tiny part:
    “Of course we still assume the user knows exactly which wires to cut and in what order, and the chemical structure of all known explosives. Our framework is just a thin, super-efficient wrapper around that already existing knowledge.”

    • Sven Slootweg

      When using a proper database API – that is, one that uses parameterized queries – escaping is not necessary, as SQL injection cannot occur. Escaping of values was just a workaround to deal with database APIs that relied on string concatenation (which has more to do with the history of MySQL than anything else).

  • thebearingedge

    After reading through the (really good, imo) docs, I’ll just try it.