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. A more Feathers specific overview can be found here.

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 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 (before, after or error)

      Writeable properties are:

      • context.params - The service method call params. For external calls, params usually contains:
        • 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 rest, socketio, primus. Will be undefined for internal calls.
      • context.id - The id for a get, remove, update and patch service method call
      • context.data - The data sent by the user in a create, update and patch service method call
      • context.error - The error that was thrown (in error hooks)
      • context.result - The result of the service method call (in after 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:

        feathers generate hook prompts

        A hook was generated and wired up to the selected service. Now it's time to add some code.

          This validation code includes:

          1. A check if there is a text in the data and throws an error if not
          2. Truncate the message's text property to 400 characters
          3. 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
          

          feathers generate hook prompts

            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 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.