Core
Guides v2.x
2

Event System #

Most of the internal tasks performed by Kuzzle trigger events.

Kuzzle enables to attach business-logic to these events by defining hooks (which allow to perform additional actions when the event triggers) and pipes (which change the behavior of the standard logic when the event triggers).

The complete list of events is available here: Internal Events List

You can display events triggered by Kuzzle by setting the DEBUG environment variable to kuzzle:events:*.

Pipe #

Kuzzle allows to modify API actions behavior with a very precise middleware-like system.

This system allows to modify the execution flow of requests processed by Kuzzle.

Pipes are functions plugged to events, called synchronously by Kuzzle, and receiving information regarding that event.

Pipes can:

  • Change the received information. Kuzzle will use the updated information upon resuming the task
  • Abort a task. If a pipe throws an error, Kuzzle interrupts the task, and forwards a standardized version of the thrown error to the originating client

pipe workflow

Each event carries a different payload. This payload must be returned by the pipe function so Kuzzle can continue its execution process.

The execution order of the pipes is decided by Kuzzle at runtime. Your pipes should be independent from one another.

Examples of pipes usage:

Registering a pipe #

We need to use the Backend.pipe.register method to register new pipes. This method takes an event name as its first parameter, followed by the pipe handler function.

Each event has a different payload.
The pipe handler function must return a promise resolving to the received payload.

It is possible to register several pipes on the same event by calling several times the Backend.pipe.register method.

When an event has more than one payload then only the first argument of the handler function must be returned. (e.g. Generic Document Events)

Example: Changing the result of the server:now API action

Copied to clipboard!
import { KuzzleRequest } from 'kuzzle';

app.pipe.register('server:afterNow', async (request: KuzzleRequest) => {
  request.result.now = (new Date()).toUTCString();

  return request;
});

As pipes are executed synchronously by Kuzzle, they can increase the execution time of a request.
A pipe that takes a long time to execute will generate an alert message in the logs. This warning can be configured under the plugins.pipeWarnTime configuration key.

Aborting a task #

When the pipe handler function returns a rejected promise or throws an error, Kuzzle aborts the current task.

If the error is one of the available default errors then the response returned to the client will contain the error as is, otherwise the error will be wrapped in a PluginImplementationError error.

Example: Limit reading access to documents to their creator

Copied to clipboard!
import { Document, KuzzleRequest, Backend, ForbiddenError } from 'kuzzle';

app.pipe.register(
    'generic:document:afterGet', 
    async (documents: Document[], request: KuzzleRequest) => {
      for (const document of documents) {
        if (request.getKuid() !== document._source._kuzzle_info.creator) {
          throw new ForbiddenError('Unauthorized access');
        }
      }

      return documents;
    });

Generic Document Events have a payload consisting of two arguments: an array of documents and the original KuzzleRequest object

Hooks #

Kuzzle allows to execute additional logic upon events.

Hooks are functions plugged to events, called asynchronously by Kuzzle, and receiving information regarding that event.

hook workflow

In general, hooks are used to perform background tasks which may otherwise slow down the request execution process.

Examples of hooks usage:

  • enrich the request with external information
  • notify user registration

Registering a hook #

We need to use the Backend.hook.register method to register new hooks. This method takes an event name as its first parameter, followed by the hook handler function.

It is possible to register several hooks on the same event by calling several times the Backend.hook.register method.

Example: Use the pub/sub engine to log user registration

Copied to clipboard!
app.hook.register('security:afterCreateRestrictedUser', async (request: KuzzleRequest) => {
  app.log.info(`New user registered: ${JSON.stringify(request.getUser())}`);
});

Handling errors #

When a hook handler function returns a rejected promise or throw an error then the hook:onError is triggered.

Handler function attached to this event will receive the following arguments:

Arguments Type Description
pluginName String Application or plugin name
event String Original event to which the hook was attached
error Error Error object
Copied to clipboard!
app.hook.register(
  'hook:onError', 
  async (pluginName: string, event: string, error: Error) => {
    app.log.error(`Error occured on event "${event}": ${error}`);
  });

To prevent infinite loops, if a hook attached to the hook:onError event fails, it won't trigger any other events.

Trigger Events #

You can only trigger custom events during the runtime phase, after the application has started.

Internal or custom events can be triggered with the Backend.trigger method.

Pipes and hooks can be plugged on custom events as well as on internal events.

It's considered a good practice to prefix your event name with your application name.

Example: Trigger a custom event

Copied to clipboard!
await app.trigger('app-name/file-available', fileUrl);

If an internal event is triggered, the payload must be the same as the original event.

Events are not triggered with the embedded SDK and controllers actions.

By default, actions executed through the embedded SDK or controllers won't trigger any events and thus no pipe or hooks will be called. On the contrary, controllers accessed by the external SDK through HTTP or Websocket requests will always fire events.

This behaviour is set in order to prevent an infinite loop in which a pipe calls a controller generating an event calling this same pipe again and again.

It is nonetheless possible to pass the flag triggerEvents to the options parameters of the controller to fire events :

Copied to clipboard!
await this.sdk.document.create(
              "index",
              'collection',
              {
                contentOfDocument: 'CREATED VIA CONTROLLER',
              },
              _idOfDocument,
              { triggerEvents: true },
            );