Bookshelf: Creating a reusable module having bookshelf models

Created on 13 Jan 2016  路  4Comments  路  Source: bookshelf/bookshelf

How can someone create a resuable express module, which contains bookshelf models, organised in multiple separate files? I am running into an initialization issue.

To expand on this, let's assume that I want to create a module mymod. The module has an index file (./index.js):

function MyMod(bookshelf) {
  this.user = bookshelf.model('User', {
    tableName: 'user',
  });
}
module.exports = function(bookshelf) {
  return new MyMod(bookshelf);
};

Then in the main app:

var bookshelf = ... // init bookshelf as normal
var mymod = require('./mymod')(bookshelf);
mymod.user.fetchAll().then(function(users) {console.log(users); });

The above works fine. The problem is that my module has lots of models, and I would like to separate them into files. A first attempt is to simply add a model filemodels/user.js. In ./index:

function MyMod(bookshelf) {
  this.bookshelf = bookshelf;
  this.user = require('./models/user');
}
module.exports = function(bookshelf) {
  return new MyMod(bookshelf);
};
//or even
//module.exports.user = require('./modelst/user');

And in models/user:

var bookshelf  = require('../').bookshelf;
module.exports = bookshelf.model('User2', {
  tableName: 'user',
});

This does not work. model/user gets executed by node before mymod is initialised, resulting upon startup in

TypeError: undefined is not a function

So I thought to separate index into a separate file, say services, require it first and then have model to require services:

In ./services/bookshelf.js:

function MyBookshelf(bookshelf) {
  this.bookshelf = bookshelf;
}
module.exports = function(bookshelf) {
  return new MyBookshelf(bookshelf);
};

In ./index:

function MyMod(bookshelf) {
  this.bookshelf = require('./services/bookshelf')(bookshelf);
  this.user = require('./models/user');
}

In 'models/user`:

var bookshelf  = require('./services/bookshelf').bookshelf;

Again runinng into the same issue.

If I change the model as:

module.exports = function() {
  return bookshelf.model('User', {
    tableName: 'user',
  });
};

It does not complain on startup, but complains on fetching:

mymod.user.fetchAll().then(function(users) {console.log(users); });

TypeError: undefined in not a function

I have not found any documentation on the subject. I might be missing something obvious in my tiredness, as I am struggling with this a couple of days. So a hint would be very much appreciated.

question

Most helpful comment

It's best to call model files with the bookshelf instance, instead of requiring bookshelf in the model files.

Using the registry plugin is strongly recommended. I do it like this to import a bunch of models into my application:

// this is the file that will be required by the app whenever it needs to access a model

var dbConfig = require('./config').db
var fs = require('fs')
var knex = require('knex')(dbConfig)
var bookshelf = module.exports = require('bookshelf')(knex)
var models = fs.readdirSync('./models')

bookshelf.plugin('registry')

models.forEach(function(model) {
    require('../models/' + model)(bookshelf)
})

This is an example model:

// I actually do some more stuff in the models but it's not important for this example

module.exports = function(db) {
    var instanceProperties = {
        tableName: 'categories',
        foos: function() {
            return this.hasMany('Foo')
        }
    }
    var Category = db.Model.extend(instanceProperties)

    return db.model('Category', Category)
}

Then to access a model, you just require the first example file above, which would be something like:

var db = require('../db')
var ExampleModel = db.model('ExampleModel')

ExampleModel.fetchAll().then(function(models) {
  // ...
})

All 4 comments

It's best to call model files with the bookshelf instance, instead of requiring bookshelf in the model files.

Using the registry plugin is strongly recommended. I do it like this to import a bunch of models into my application:

// this is the file that will be required by the app whenever it needs to access a model

var dbConfig = require('./config').db
var fs = require('fs')
var knex = require('knex')(dbConfig)
var bookshelf = module.exports = require('bookshelf')(knex)
var models = fs.readdirSync('./models')

bookshelf.plugin('registry')

models.forEach(function(model) {
    require('../models/' + model)(bookshelf)
})

This is an example model:

// I actually do some more stuff in the models but it's not important for this example

module.exports = function(db) {
    var instanceProperties = {
        tableName: 'categories',
        foos: function() {
            return this.hasMany('Foo')
        }
    }
    var Category = db.Model.extend(instanceProperties)

    return db.model('Category', Category)
}

Then to access a model, you just require the first example file above, which would be something like:

var db = require('../db')
var ExampleModel = db.model('ExampleModel')

ExampleModel.fetchAll().then(function(models) {
  // ...
})

The above makes great documentation on the subject. Ricardo thanks a lot for your timely response, it is very helpful. Haven't thought of that at all, I will give a try and get back.

In case of an app with a separate reusable module (which would contain the models), db.js (the first piece of code) would have to live in the app, so the requires of the models would have to point to the module path, correct?

Is there some way to make bookshelf initialisation into the module? This would need to provide the configuration into the module somehow.

To answer my own question:

The important stuff is in the model's code. Ricardo's sample properly illustrates how to code the model.

Then in a reusable's index file:

function MyModule(db) {
  this.user = require('./models/user')(db);
}
module.exports = function(db) {
  return new MyModule(db);
};

In the application's bookshelf file:

var knexFile = require('../knexfile.js');
var knex = require('knex')(knexFile.development);
var bookshelf = require('bookshelf')(knex);
bookshelf.plugin('registry');
module.exports = bookshelf;

// example of using a model from a reusable module
var user = require('mymodule')(bookshelf).user;
module.exports.user = user;
Was this page helpful?
0 / 5 - 0 ratings

Related issues

KieronWiltshire picture KieronWiltshire  路  3Comments

MarkHerhold picture MarkHerhold  路  3Comments

Playrom picture Playrom  路  4Comments

osher picture osher  路  3Comments

leebenson picture leebenson  路  4Comments