A remote hook runs before or after execution of a model's remote method.
Page Contents

Overview

LoopBack provides three kinds of hooks:

  • Remote hooks, that execute before or after calling a remote method, either a custom remote method  or a standard create, retrieve, update, and delete method inherited from PersistedModel. See PersistedModel REST API for information on how the Node methods correspond to REST operations.
  • Operation hooks that execute when models perform create, retrieve, update, and delete operations. Operation hooks replace deprecated model hooks.
  • Connector hooks that execute before requests to a data source connector or after the connector’s response.

remote hook enables you to execute a function before or after a remote method is called by a client:

  • beforeRemote() runs before the remote method.
  • afterRemote() runs after the remote method has finished successfully.
  • afterRemoteError() runs after the remote method has finished with an error.

Signature

Both beforeRemote() and afterRemote() have the same signature; below syntax uses beforeRemote but afterRemote is the same.

For static custom remote methods:

modelName.beforeRemote( methodName, function( ctx, next) {
    //...
    next();
});

Using async/await

Remote hooks can also return a promise instead of using the next parameter

modelName.beforeRemote( methodName, async function( ctx) {
    //...
    return;
});

Where:

  • modelName is the name of the model that has the remote method.
  • methodName is the name of the remote method.

Instance methods and static built-in methods such as upsert() or  create() require a third argument in the callback:

modelName.beforeRemote( methodName, function( ctx, modelInstance, next) {
    //...
    next();
});

The hook afterRemoteError() has a slightly different signature: The handler function has only two arguments:

modelName.afterRemoteError( methodName, function( ctx, next) {
    //...
    next();
});

Where:

  • modelName is the name of the model to which the remote hook is attached.
  • methodName is the name of the method that triggers the remote hook. This may be a custom remote method or a standard create, retrieve, update, and delete method inherited from PersistedModel. It may include wildcards to match more than one method (see below).
  • ctx is the context object
  • modelInstance is the affected model instance.

The syntax above includes a call to next() as a reminder that you must call next() at some point in the remote hook callback function. It doesn’t necessarily have to come at the end of the function, but must be called at some point before the function completes.

Wildcards

You can use the following wildcard characters in methodName:

  • Asterisk '*' to match any character, up to the first occurrence of the delimiter character '.' (period).
  • Double-asterisk to match any character, including the delimiter character '.' (period).

For example, use '*.*' to match any static method; use 'prototype.*' to match any instance method.

Examples

The following example defines beforeRemote and afterRemote hooks for the revEngine() remote method:

common/models/car.js

module.exports = function(Car) {
  // remote method
  Car.revEngine = function(sound, cb) {
    cb(null, sound - ' ' - sound - ' ' - sound);
  };
  Car.remoteMethod(
    'revEngine',
    {
      accepts: [{arg: 'sound', type: 'string'}],
      returns: {arg: 'engineSound', type: 'string'},
      http: {path:'/rev-engine', verb: 'post'}
    }
  );
  // remote method before hook
  Car.beforeRemote('revEngine', function(context, unused, next) {
    console.log('Putting in the car key, starting the engine.');
    next();
  });
  // remote method after hook
  Car.afterRemote('revEngine', function(context, remoteMethodOutput, next) {
    console.log('Turning off the engine, removing the key.');
    next();
  });
...
}

The following example uses wildcards in the remote method name. This remote hook is called whenever any remote method whose name ends with “save” is executed:

common/models/customer.js

Customer.beforeRemote('*.save', function(ctx, unused, next) {
  if(ctx.req.accessToken) {
    next();
  } else {
    next(new Error('must be logged in to update'))
  }
});

Customer.afterRemote('*.save', function(ctx, user, next) {
  console.log('user has been saved', user);
  next();
});

Below are more examples of remote hooks with wildcards to run a function before any remote method is called.

common/models/customer.js

// ** will match both prototype.* and *.*
Customer.beforeRemote('**', function(ctx, user, next) {
  console.log(ctx.methodString, 'was invoked remotely'); // customers.prototype.save was invoked remotely
  next();
});

Other wildcard examples
// run before any static method eg. User.find
Customer.beforeRemote('*', ...);

// run before any instance method eg. User.prototype.save
Customer.beforeRemote('prototype.*', ...);

// prevent password hashes from being sent to clients
Customer.afterRemote('**', function (ctx, user, next) {
  if(ctx.result) {
    if(Array.isArray(ctx.result)) {
      ctx.result.forEach(function (result) {
        result.unsetAttribute('password');
      });
    } else {
      ctx.result.unsetAttribute('password');
    }
  }

  next();
});

A safer means of effectively white-listing the fields to be returned by copying the values into new objects:

common/models/account.js

var WHITE_LIST_FIELDS = ['account_id', 'account_name'];

Account.afterRemote('**', function(ctx, modelInstance, next) {
  if (ctx.result) {
    if (Array.isArray(modelInstance)) {
      var answer = [];
      ctx.result.forEach(function (result) {
        var replacement ={};
        WHITE_LIST_FIELDS.forEach(function(field) {
          replacement[field] = result[field];
        });
        answer.push(replacement);
      });
    } else {
      var answer ={};
      WHITE_LIST_FIELDS.forEach(function(field) {
        answer[field] = ctx.result[field];
      });
    }
    ctx.result = answer;
  }
  next();
});

Examples of afterRemoteError

Perform an additional action when the instance method speak() fails:

common/models/dog.js

Dog.afterRemoteError('prototype.speak', function(ctx, next) {
  console.log('Cannot speak!', ctx.error);
  next();
});

Attach extra metadata to error objects:

common/models/dog.js

Dog.afterRemoteError('**', function(ctx, next) {
  if (!ctx.error.details) ctx.error.details = {};
  ctx.error.details.info = 'intercepted by a hook';
  next();
})

Report a different error back to the caller:

common/models/dog.js

Dog.afterRemoteError('prototype.speak', function(ctx, next) {
  console.error(ctx.error);
  next(new Error('See server console log for details.'));
});

Context object

Remote hooks are provided with a Context ctx object that contains transport-specific data (for HTTP: req and res). The ctx object also has a set of consistent APIs across transports.

Applications that use loopback.rest() middleware provide the following additional ctx properties:

  • ctx.req: Express Request object. 

  • ctx.res: Express Response object.

The context object passed to afterRemoteError() hooks has an additional property ctx.error set to the Error reported by the remote method.

Other properties:

  • ctx.args - Object containing the HTTP request argument definitions. Uses the arg definition to find the value from the request. These are the input values to the remote method.
  • ctx.result - An object keyed by the argument names. Exception: If the root property is true, then it’s the value of the argument that has root set to true. 

ctx.req.accessToken

The accessToken of the user calling the remote method.

ctx.result

During afterRemote hooks, ctx.result will contain the data about to be sent to a client. Modify this object to transform data before it is sent.

If a remote method explicitly specifies the returned value, only then would ctx.result be set. So your remote method must do something like:

MyModel.remoteMethod('doSomething', {
  // ...
  returns: {arg: 'redirectUrl', type: 'string'}
});