There are many ways in which hooks can be used. Below are some examples that show how to quickly add useful functionality to a service. The authentication chapter has more examples on how to use hooks for user authorization.
If the database adapter does not support it already, timestamps can be easily added as a before hook like this:
app.service('todos').before({
create(hook) {
hook.data.created_at = new Date();
},
update(hook) {
hook.data.updated_at = new Date();
},
patch(hook) {
hook.data.updated_at = new Date();
}
})
Hooks can also be used to fetch related items from other services. The following hook checks for a related
query parameter in get
and if it exists, includes all todos for the user in the response. Because it is an after hook, hook.result
will already be populated with data from the original query. In this case, it's on a service.get
request, so hook.result
will contain a user object. The hook fetches data from the todos
service and creates a todos
property on the user object in hook.result:
app.service('users').after({
get(hook) {
// The user id
const id = hook.result.id;
if(hook.params.query.related) {
return hook.app.service('todos').find({
query: { user_id: id }
}).then(todos => {
// Set the todos on the user property
hook.result.todos = todos;
// Always return the hook object or `undefined`
return hook;
});
}
}
});
For a production app you need to do validation. With hooks it's actually pretty easy and validation methods can easily be reused.
app.service('users').before({
create(hook) {
// Don't create a user unless they accept our terms
if (!hook.data.acceptedTerms) {
throw new errors.BadRequest(`Invalid request`, {
errors: [
{
path: 'acceptedTerms',
value: hook.data.acceptedTerms,
message: `You must accept the terms`
}
]
});
}
}
});
You might also need to correct or sanitize data that is sent to your app. Also pretty easy and you can check out some of our bundled hooks that cover some common use cases.
app.service('users').before({
update(hook) {
// Convert the user's age to an integer
const sanitizedAge = parseInt(hook.data.age, 10);
if (isNaN(age)) {
throw new errors.BadRequest(`Invalid 'age' value ${hook.data.age}`, {
errors: [
{
path: 'age',
value: hook.data.age,
message: `Invalid 'age' value ${hook.data.age}`
}
]
});
}
hook.data.age = sanitizedAge;
}
});
Sometimes you might not want to actually delete items in the database but just mark them as deleted. We can do this by adding a remove
before hook, marking the todo as deleted and then setting hook.result
. That way we can completely skip the original service method.
app.service('todos').before({
remove(hook) {
// Instead of calling the service remote, call `patch` and set deleted to `true`
return this.patch(hook.id, { deleted: true }, hook.params).then(data => {
// Set the result from `patch` as the method call result
hook.result = data;
// Always return the hook or `undefined`
return hook;
});
},
find(hook) {
// Only query items that have not been marked as deleted
hook.params.query.deleted = { $ne: true };
}
});
ProTip: Setting
hook.result
will only skip the actual method. after hooks will still run in the order they have been registered.