Search code examples
node.jsloopbackjsstrongloop

loopbackjs: Attach a model to different datasources


I have defined several models that use a Datasource "db" (mysql) for my environment.

Is there any way to have several datasources attached to those models, so I would be able to perform REST operations to different databases?

i.e: GET /api/Things?ds="db"

GET /api/Things?ds="anotherdb"

GET /api/Things (will use default ds)


Solution

  • As @superkhau pointed above, each LoopBack Model can be attached to a single data-source only.

    You can create (subclass) a new model for each datasource you want to use. Then you can either expose these per-datasource models via unique REST URLs, or you can implement a wrapper model that will dispatch methods to the correct datasource-specific model.

    In my example, I'll show how to expose per-datasource models for a Car model that is attached to db and anotherdb. The Car model is defined in the usual way via common/models/car.json and common/models/car.js.

    Now you need to define per-datasource models:

    // common/models/car-db.js
    {
      "name": "Car-db",
      "base": "Car",
      "http": {
        "path": "/cars:db"
      }
    }
    
    // common/models/car-anotherdb.js
    {
      "name": "Car-anotherdb",
      "base": "Car",
      "http": {
        "path": "/cars:anotherdb"
      }
    
    }
    
    // server/model-config.json
    {
      "Car": {
        "dataSource": "default"
      },
      "Car-db": {
        "dataSource": "db"
      },
      "Car-anotherdb": {
        "dataSource": "anotherdb"
      }
    }
    

    Now you have the following URLs available:

    GET /api/Cars:db
    GET /api/Cars:anotherdb
    GET /api/Cars
    

    The solution outlined above has two limitations: you have to define a new model for each datasource and the datasource cannot be selected using a query parameter.

    To fix that, you need a different approach. I'll again assume there is a Car model already defined.

    Now you need to create a "dispatcher".

    // common/models/car-dispatcher.json
    {
      "name": "CarDispatcher",
      "base": "Model", //< important!
      "http": {
        "path": "/cars"
      }
    }
    
    // common/models/car-dispatcher.js
    var loopback = require('loopback').PersistedModel;
    module.exports = function(CarDispatcher) {
      Car.find = function(ds, filter, cb) {
        var model = this.findModelForDataSource(ds);
        model.find(filter, cb);
      };
    
      // a modified copy of remoting metadata from loopback/lib/persisted-model.js
      Car.remoteMethod('find', {
        isStatic: true,
        description: 'Find all instances of the model matched by filter from the data source',
        accessType: 'READ',
        accepts: [
         {arg: 'ds', type: 'string', description: 'Name of the datasource to use' },
         {arg: 'filter', type: 'object', description: 'Filter defining fields, where, orderBy, offset, and limit'}
        ],
        returns: {arg: 'data', type: [typeName], root: true},
        http: {verb: 'get', path: '/'}
      });
    
      // TODO: repeat the above for all methods you want to expose this way
    
      Car.findModelForDataSource = function(ds) {
        var app = this.app;
        var ds = ds && app.dataSources[ds] || app.dataSources.default;
    
        var modelName = this.modelName + '-' + ds;
        var model = loopback.findModel(modelName);
        if (!model) {
          model = loopback.createModel(
            modelName, 
            {},
            { base: this.modelName });
        }
    
        return model;
      };  
    };
    

    The final bit is to remove Car and use CarDispatcher in the model config:

    // server/model-config.json
    {
      "CarDispatcher": {
        dataSource: null,
        public: true
      }
    }