How to Structure Bookshelf.js Models

I’m starting my second project using the excellent Bookshelf.js ORM (for relational databases) for Node.js and I’ve noticed that unless you’re a JavaScript pro it’s easy to screw up the structure of your models and get circular dependency errors. I love Bookshelf but the documentation has actually gotten worse recently so I decided I’d document how to structure your models in different files the correct way using Bookshelf.js.

The problem

When you’re developing in any language it’s always a good idea to modularize your code. Bookshelf models are no exception to this rule. Unfortunately, the first time I used this neat package I threw my hands up and ended up defining all of my models in one file, in one big exported object. It wasn’t modular. The reason I did this was because of circular dependency errors.

It’s likely that you’ll need to do something like this with your code at some point (example taken directly from the Bookshelf wiki)

1
2
3
4
5
6
7
8
9
10
11
12
13
// file: a.js
var b = require('./b'); // => {}
module.exports = 'foo';

// file: b.js
var a = require('./a'); // => {}
module.exports = function() {
  return 'bar';
};

//file: c.js
var b = require('./b');
b(); // => TypeError, a is not a function

That’s a circular dependency error right there. And if your models have any relations that’s exactly what’ll happen if you follow the Bookshelf docs as written and put each model in a separate file.

The solution

Please note that I’m using Node v5+ here and taking advantage of some ES6 features. If you’re not using ES6 yet be sure to change my lets to vars

Create a reusable Bookshelf instance

The first thing you need to do is create a file that’ll export an instance of Bookshelf when required. Because of how Node require calls work, each time you require this file you’ll always get back the same instance of Bookshelf so you don’t need to worry about new connection pools being opened each time you reference it. The file itself is pretty simple. Just a reference to Knex, Bookshelf, then a line that exports the Bookshelf instance.

1
2
3
4
5
6
7
8
9
// database.js
'use strict';

var knex      = require('knex')(require('./knexfile')[process.env.NODE_ENV]), // Selects the correct DB config object for the current environment
    bookshelf = require('bookshelf')(knex);

bookshelf.plugin('registry'); // Resolve circular dependencies with relations

module.exports = bookshelf;

The one line to take note of is the one that reads bookshelf.plugin('registry');. That declares that you’ll be using the Model Registry plugin. This plugin allows you to specify relations between models using a string instead of passing variables. You can read more about how it works on the wiki but for our purposes what you need to know is that it takes care of those circular dependencies.

Creating the models

Now that you have a module for the database you can focus on your models. Let’s suppose that you have an application with users and those users generate invoices so we have a User and Invoice model. We’ll create those here:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// user.js
'use strict';

let Bookshelf = require('./database');

require('./invoice');
var User = Bookshelf.Model.extend({
  tableName: 'users',
  hasTimestamps: true,

  invoices: function() {
    return this.hasMany('Invoice');
  }
});

module.exports = Bookshelf.model('User', User);

Again, a pretty basic model. You access Bookshelf not by requiring it directly but instead by requiring your database.js file which returns the same instance of Bookshelf wherever it gets called. The line to look at is the last one, the exports call. Here we’re registering our User model as the string 'User'. Now we’ll be able to reference the User model using a string rather than assigning it to a variable (that’s what gets us the circular dependency error to begin with).

Another thing to note is that we’re require()ing our Invoice model but not assigning it to a variable. The Registry plugin expects you to simply require the model you need as a relation, it’s perfectly fine if you don’t assign it to a variable (and in fact you should not assign it to a variable).

So now let’s build the invoice model:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
'use strict';

let Bookshelf = require('../config/database');

require('./user');
var Invoice = Bookshelf.Model.extend({
  tableName: 'invoices',
  hasTimestamps: true,

  user: function() {
    return this.belongsTo('User');
  }
});

module.exports = Bookshelf.model('Invoice', Invoice);

The Invoice model is created exactly like the User model so I won’t explain the code a second time.

Using the models and relations

Now that the models are set up and the relations are defined we can use them in our code. Here’s an example of what you’d write within an Express route to get invoices related to a user.

1
2
3
4
5
6
7
8
9
10
11
12
13
let User = require('./user');
app.get('/example/?', function(req, res, next) {
  var userId = req.session.userId; // assume the User ID is stored in a session variable

  User.where({id: userId}).fetch({withRelated: ['invoices']})
      .then(function(user) {
          console.log(user.related('invoices'));
          res.json(user);
      })
      .catch(function(err) {
          return next(new Error(err));
      });
});

And that’ll get you the JSON representation of your user in the browser and the invoices relation in your console. Once you set it up it seems obvious. Its just too bad the Bookshelf docs don’t make this clearer.

Web development

« Understanding Rails by Building a Ruby Web Framework from Scratch Organizing Express Routes »

Comments