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.
A 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.
Tip: Use beforeRemote hooks to validate and sanitize inputs to a remote method. Because a beforeRemote hook runs before the remote method is executed, it can access the inputs to the remote method, but not the result.
Use afterRemote hooks to modify, log, or otherwise use the results of a remote method before sending it to a remote client. Because an afterRemote hook runs after the remote method is executed, it can access the result of the remote method, but cannot modify the input arguments.
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:
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:
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();
});
Important:
The second argument to the hook (user
in the above example) is the ctx.result
which is not always available.
Below are more examples of remote hooks with wildcards to run a function before any remote method is called.
// ** 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:
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:
Dog.afterRemoteError('prototype.speak', function(ctx, next) {
console.log('Cannot speak!', ctx.error);
next();
});
Attach extra metadata to error objects:
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:
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:
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.
Important:
ctx.req.accessToken
is undefined if the remote method is not invoked by a logged in user (or other principal).
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.
Important:
The value of ctx.result
may not be available at all times.
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'}
});