A common use of helpers is to encapsulate some repeated database queries. For example, suppose our app had a User
model which included a field lastActiveAt
which tracked the time of their last login. A common task in such an app might be to retrieve the list of users most recently online. Rather than hard-coding this query into multiple locations, we could write a helper instead:
// api/helpers/get-recent-users.js
module.exports = {
friendlyName: 'Get recent users',
description: 'Retrieve a list of users who were online most recently.',
extendedDescription: 'Use `activeSince` to only retrieve users who logged in since a certain date/time.',
inputs: {
numUsers: {
friendlyName: 'Number of users',
description: 'The maximum number of users to retrieve.',
type: 'number',
defaultsTo: 5
},
activeSince: {
description: 'Cut-off time to look for logins after, expressed as a JS timestamp.',
extendedDescription: 'Remember: A _JS timestamp_ is the number of **milliseconds** since [that fateful night in 1970](https://en.wikipedia.org/wiki/Unix_time).',
type: 'number',
defaultsTo: 0
}
},
exits: {
success: {
outputFriendlyName: 'Recent users',
outputDescription: 'An array of users who recently logged in.',
},
noUsersFound: {
description: 'Could not find any users who logged in during the specified time frame.'
}
},
fn: async function (inputs, exits) {
// Run the query
var users = await User.find({
active: true,
lastLogin: { '>': inputs.activeSince }
})
.sort('lastLogin DESC')
.limit(inputs.numUsers);
// If no users were found, trigger the `noUsersFound` exit.
if (users.length === 0) {
throw 'noUsersFound';
}
// Otherwise return the records through the `success` exit.
return exits.success(users);
}
};
To call this helper from app code using the default options (in an action, for example), we would use:
var users = await sails.helpers.getRecentUsers();
To alter the criteria for the returned users, we could pass in some values:
var users = await sails.helpers.getRecentUsers(50);
Or, to get the 10 most recent users who have logged in since St. Patrick's Day, 2017:
await sails.helpers.getRecentUsers(10, (new Date('2017-03-17')).getTime());
Note: These values passed into a helper at runtime are sometimes called argins, or options, and they correspond with the key order of the helper's declared input definitions (e.g.
numUsers
andactiveSince
).
Again, chaining .with()
in order to use named parameters:
await sails.helpers.getRecentUsers.with({
numUsers: 10,
activeSince: (new Date('2017-03-17')).getTime()
});
Finally, to handle the noUsersFound
exit explicitly rather than simply treating it like any other error, we can use .intercept()
or .tolerate()
:
var users = await sails.helpers.getRecentUsers(10)
.tolerate('noUsersFound', ()=>{
// ... handle the case where no users were found. For example:
sails.log.verbose(
'Worth noting: Just handled a request for active users during a time frame '+
'where no users were found. Anyway, I didn\'t think this was possible, because '+
'our app is so cool and popular. But there you have it.'
);
});
var users = await sails.helpers.getRecentUsers(10)
.intercept('noUsersFound', ()=>{
return new Error('Inconceivably, no active users were found for that timeframe.');
});
The main advantage of using helpers is the ability to update functionality in many parts of an app by changing code in a single place. For example, by changing the default value of numUsers
from 5
to 15
, we update the size of the default list returned in any place that uses the helper. Also, by using well-defined inputs like numUsers
and activeSince
, we guarantee we’ll get helpful errors if we accidentally use an invalid (i.e. non-numeric) value.
A few more notes about the example getRecentUsers()
helper above:
- Many of the fields such as
description
andfriendlyName
are not strictly required but are immensely helpful in keeping the code maintainable, especially when sharing the helper across multiple apps.- The
noUsersFound
exit may or may not be helpful, depending on your app. If you always want to perform a specific action when no users are returned (for example, redirecting to a different page), this exit would be a good idea. On the other hand, if you simply want to tweak some text in a view based on whether or not users were returned, it might be better to just have thesuccess
exit and check thelength
of the returned array in your action or view code.