# Hooks
As we have seen in the services chapter, Feathers services are a great way to implement data storage and modification. Technically, we could implement our entire app with services but very often we need similar functionality across multiple services. For example, we might want to check for all services if a user is allowed to even use it or add the current date to all data that we are saving. With just using services we would have to implement this again every time.
This is where Feathers hooks come in. Hooks are pluggable middleware functions that can be registered before, after or on errors of a service method. You can register a single hook function or create a chain of them to create complex work-flows. In this chapter we will learn more about hooks and create workflows to process new chat messages.
Just like services themselves, hooks are transport independent. They are usually also service agnostic, meaning they can be used with any service. This pattern keeps your application logic flexible, composable, and much easier to trace through and debug.
Note: A full overview of the hook API can be found in the hooks API documentation.
Hooks are commonly used to handle things like validation, authorization, logging, populating related entities, sending notifications and more.
Pro tip: For the general design pattern behind hooks see this blog post (opens new window). A more Feathers specific overview can be found here (opens new window).
# Quick example
Here is a quick example for a hook that adds a createdAt
property to the data before calling the actual create
service method:
# Hook functions
A hook function is a function that takes the hook context as the parameter and returns that context or nothing. Hook functions run in the order they are registered and will only continue to the next once the current hook function completes. If a hook function throws an error, all remaining hooks (and the service call if it didn't run yet) will be skipped and the error will be returned.
A common pattern the generator uses to make hooks more re-usable (e.g. making the createdAt
property name from the example above configurable) is to create a wrapper function that takes those options and returns a hook function:
Now we have a re-usable hook that can set the timestamp on any property.
# Hook context
The hook context
is an object which contains information about the service method call. It has read-only and writable properties.
Read-only properties are:
context.app
- The Feathers application object. This can be used to e.g. call other servicescontext.service
- The service this hook is currently running oncontext.path
- The path (name) of the servicecontext.method
- The service method namecontext.type
- The hook type (before
,after
orerror
)
Writeable properties are:
context.params
- The service method callparams
. For external calls,params
usually contains:context.params.query
- The query (e.g. query string for REST) for the service callcontext.params.provider
- The name of the transport (which we will look at in the next chapter) the call has been made through. Usuallyrest
,socketio
,primus
. Will beundefined
for internal calls.
context.id
- Theid
for aget
,remove
,update
andpatch
service method callcontext.data
- Thedata
sent by the user in acreate
,update
andpatch
service method callcontext.error
- The error that was thrown (inerror
hooks)context.result
- The result of the service method call (inafter
hooks)
Note: For more information about the hook context see the hooks API documentation.
# Registering hooks
In a Feathers application generated by the CLI, hooks are being registered in a .hooks
file in an object in the following format:
This makes it easy to see at one glance in which order hooks are executed and for which method.
Note:
all
is a special keyword which means those hooks will run before the method specific hooks in this chain.
# Processing messages
Cool. Now that we learned about hooks we can add two hooks to our messages service that help sanitize new messages and add information about the user that sent it.
# Sanitize new message
When creating a new message, we automatically sanitize our input, add the user that sent it and include the date the message has been created before saving it in the database. In this specific case it is a before hook. To create a new hook we can run:
feathers generate hook
Let's call this hook process-message
. We want to pre-process client-provided data. Therefore, in the next prompt asking for what kind of hook, choose before
and press Enter.
Next a list of all our services is displayed. For this hook, only choose the messages
service. Navigate to the entry with the arrow keys and select it with the space key, then confirm with enter.
A hook can run before any number of service methods. For this specific hook, only select create
. After confirming the last prompt you should see something like this:
A hook was generated and wired up to the selected service. Now it's time to add some code.
This validation code includes:
- A check if there is a
text
in the data and throws an error if not - Truncate the message's
text
property to 400 characters - Update the data submitted to the database to contain:
- The new truncated text
- The currently authenticated user id (so we always know who sent it)
- The current (creation) date
# Populate the message sender
In the process-message
hook we are currently just adding the user's _id
as the userId
property in the message. We want to show more information about the user that sent it in the UI, so we'll need to populate more data in the message response.
We can do this by creating another hook called populate-user
which is an after
hook on the messages
service for all
methods:
feathers generate hook
Note:
Promise.all
makes sure that all asynchronous operations complete before returning all the data.
Pro tip: This is one way to associate data in Feathers. For more information about associations see this FAQ.
# Hooks vs. extending services
In the previous chapter we extended our user service to add a user avatar. This could also be put in a hook instead but made a good example to illustrate how to extend an existing service. There are no explicit rules when to use a hook or when to extend a service but here are some guidelines.
Use a hook when
- The functionality can be used in more than one place (e.g. validation, permissions etc.)
- It is not a core responsibility of the service and the service can work without it (e.g. sending an email after a user has been created)
Extend a service when
- The functionality is only needed in this one place
- The service could not function without it
Create your own (custom) service when
- Multiple services are combined together (e.g. reports)
- The service does something other than talk to a database (e.g. another API, sensors etc.)
# What's next?
In this chapter we learned how Feathers hooks can be used as middleware for service method calls to validate and manipulate incoming and outgoing data without having to change our service. We now have a fully working chat application. Before we create a frontend for it though, let's first look at how authentication works with Feathers.