Matt Hamann Matt Hamann - 7 days ago 5
Node.js Question

Building a "flat" API in loopback

When building an API in Loopback, I want to have relatively flat, authentication protected routes like:

/users
,
/orders
, etc. So, if I'm a user and I have some orders, I should be able to simply call
/orders
or
/orders/:id
to get one or more of the orders for my account.

Using Loopback's model relationships, I could do something like:
/users/:userId/orders
relatively easily, but there are definitely cases where I don't want to add in that complexity to the URL structure.

Is there a good, recommended way to do something like this? Seems like there should be a way to set this up such that the current user's access token tells Loopback which records it has access to and then can return only those.

Answer

The solution to this problem wasn't terribly obvious, but I came up with something that works well enough, though it's a bit "manual."

First, in my case, I always have access to the user via req.user, which is a fairly common Node.js / Express pattern. I believe libs like Passport follow this as well.

Then, I created a generic helper method called that looks like this:

module.exports.sourceUser = function(ctx) {
  var req = ctx.req;

    if (!req.user) {
        var err = new Error('You must pass valid credentials to call this API.');
        err.statusCode = 401;
        throw err;
    }

    return req.user;
};

This will ensure that APIs I attach this method to will require user authentication, rejecting any request that doesn't have a valid user cred.

Next, since I'm defining flat APIs, I'm just defining my own remote methods (not using built-in Loopback ones). Each remote method def looks like this:

Model.remoteMethod('getRecord', {
    http: {
        verb: 'get',
        path: '/:id'
    },
    accepts: [
        { arg: 'id', type: 'string', required: true, http: { source: 'path' } },
        { arg: 'user', type: 'object', http: helpers.sourceUser }
    ],

    returns: [
        { arg: 'model', type: 'model', root: true }
    ]
});

You'll need to adjust that code to match your model names, etc. Also, notice the usage of helpers.sourceUser which is the thing that ensures we have a user and then provides that user as an argument to the API implementation.

Finally, I use a where clause in my database queries, etc to ensure that a user only gets/sets their own records (e.g. where model.user == user.id).

Hope this helps someone else...

Comments