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.
# 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
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 services
context.service- The service this hook is currently running on
context.path- The path (name) of the service
context.method- The service method name
context.type- The hook type (
Writeable properties are:
context.params- The service method call
params. For external calls,
context.params.query- The query (e.g. query string for REST) for the service call
context.params.provider- The name of the transport (which we will look at in the next chapter) the call has been made through. Usually
primus. Will be
undefinedfor internal calls.
patchservice method call
datasent by the user in a
patchservice method call
context.error- The error that was thrown (in
context.result- The result of the service method call (in
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.
allis 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
textin the data and throws an error if not
- Truncate the message's
textproperty 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
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
feathers generate hook
Promise.allmakes 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 database 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.