Loopback "hasManyThrough" relation. Where to add it?

I have a User entity and a Hobbie entity, both have their models defined in Loopback and I see them in the API explorer.

I have a table UserHobbie that links User and Hobbie in a ManyToMany relation. I'm tryting to declare a loopback hasManyThrough relation such as

User.hasMany(Hobbie, {through: UserHobbie});

but I cannot seem to do it well, for it doesn't show up in the explorer. I've declared it in /server/server.js right after the bootstrapping section, and I've tried doing it in /common/User.js and /common/Hobbie.js (but in either one of them, the other model isn't visible).

Is there a proper syntax to add this in User.json or Hobbie.json? That would be my preferred way to go, since anything I put in the json definition shows up right in the explorer.

To solve the problem within your Model JSON, I'll outline the Solution below. However, using a "hasAndBelongsToMany" Relationship would solve your problem more simply and I'll outline that below also.

Within your User.json:

  "relations": {
    "Hobbies": {
      "type": "hasMany",
      "model": "Hobbie",
      "through": "UserHobbie",
      "foreignKey": "hobbieId"
    }
  }

Within your Hobbie.json:

  "relations": {
    "Users": {
      "type": "hasMany",
      "model": "User",
      "through": "UserHobbie",
      "foreignKey": "userId"
    }
  }

Your UserHobbie.json would look like this (note that you DO NOT define userId OR hobbieId within "properties":

{
  "name": "UserHobbie",
  "plural": "UserHobbies",
  "base": "PersistedModel",
  "properties": {
    "id": {
      "type": "String",
      "id": true
    }
  },
  "validations": [],
  "relations": {
    "Users": {
        "type": "belongsTo",
        "model": "User",
        "foreignKey": "userId"
    },
    "Hobbies": {
        "type": "belongsTo",
        "model": "Hobbie",
        "foreignKey": "hobbieId"
    }
  },
  "acls": [],
  "methods": []
}

THIS SIMPLER WAY TO DO THIS IS BELOW:

DON'T EXPLICITLY CREATE A UserHobbies Model. Loopback will AUTOMATICALLY create a Join Model for you.

Within your User Model:

  "relations": {
    "Hobbies": {
      "type": "hasAndBelongsToMany",
      "model": "Hobbie"
    }
  }

Within you Hobbie Model:

  "relations": {
    "Users": {
      "type": "hasAndBelongsToMany",
      "model": "User"
    }
  }

If you want to do this in code, YOU ARE CORRECT, there are Bootstrap Timing Issues that keep these Relationships from appearing in Explorer. I will add another response soon to show you how to make that work too.

Now, to make your original Code Based Implementation of creating Relationships show up in Explorer, here is what you should do.

First, from your Loopback Projects's ./server/boot directory, move "explorer.js" to ./server (where server.js is in the project.

The last portion of ./server/server.js should look something like this (I've eliminated some comments for brevity.

boot(app, __dirname);

... // Removed for brevity

app.use(loopback.urlNotFound());

// The ultimate error handler.
app.use(loopback.errorHandler());

app.start = function() {
  // start the web server
  return app.listen(function() {
    app.emit('started');
    console.log('Web server listening at: %s', app.get('url'));
  });
};

// start the server if `$ node server.js`
if (require.main === module) {
  app.start();
}

Now EDIT the last part of .server/server.js to look like this:

boot(app, __dirname);
    // We took explorer.js out of /boot and put it in /server root next to server.js


var programmaticLoopbackSetup = require('./programmaticLoopbackSetup');
    // If the User has any special Programmatic Loopback Setup (create Model Relationships, etc.) do it first

if (programmaticLoopbackSetup !== undefined) {
    programmaticLoopbackSetup(app, finishUp);
}
else {
    finishUp();  // If you didn't want any Code based Setup
}

// Defer all the rest of the Startup Work until Explorer 
// has all the Model Info it needs from any Async or Programmatic Setup.
function finishUp() {

    require('./explorer')(app);
        // This was formerly done within "boot" above...

        ...  // removed for brevity...

    // Requests that get this far won't be handled
    // by any middleware. Convert them into a 404 error
    // that will be handled later down the chain.
    app.use(loopback.urlNotFound());

    // The ultimate error handler.
    app.use(loopback.errorHandler());

    app.start = function() {
      // start the web server
      return app.listen(function() {
        app.emit('started');
        console.log('Web server listening at: %s', app.get('url'));
      });
    };

    // start the server if `$ node server.js`
    if (require.main === module) {
      app.start();
    }
}

Add a new file at ./server/programmaticLoopbackSetup.js and make it look something like below (notice how it is called after "boot" and before finalizing the App Initialization

module.exports = function programmaticLoopbackSetup(app, next) {
    var User = app.models.User;
    var Hobbie = app.models.Hobbie;


    // This Block Below Creates a Many to Many User / Hobbie Relationship directly 

        User.hasAndBelongsToMany(Hobbie);
        Hobbie.hasAndBelongsToMany(User);

    next();  // Callback to finish App Init...
};

You will see the User / Hobbie Relationships (using "hasAndBelongsToMany" in this case) within Explorer. Do WHATEVER Code Based LoopBack Model, Datasource, or other refinements within programmaticLoopbackSetup.js