Forest provides extensive customization options for your collections, allowing you to tailor CRUD operations, implement business logic, and enhance data integrity through two features:
Collection hooks, execute custom code before or after CRUD operations
Collection overrides, completely replace the default behavior of CUD operations
These two features are almost identical, but they are executed at very different stages of the customizations.
This means that Collection hooks will be executed even if you choose to use Collection override.
Collection hooks allow you to execute custom code before or after CRUD operations, giving you the ability to enforce business rules, or integrate with external services.
Any given Collection should implement all of the following functions:
list
create
update
delete
aggregate
The Collection hooks feature allows executing code before and/or after any of these functions, providing a way to interact with your Collections.To declare a hook on a Collection, the following information is required:
A lifecycle position (Before | After)
An action type (List | Create | Update | Delete | Aggregate)
A callback, that will receive a context matching the provided hook position and hook definition.
A single Collection can have multiple hooks with the same position and the same type. They will run in their declaration order.Collection hooks are only called when the Collection function is contacted by the UI. This means that any usage of the Forest query interface will not trigger them.
In the following example, we want to prevent a set of users from updating any records of the Transactions table. We want to check if the user email is allowed to update a record via an external API call.
transaction.addHook('Before', 'Update', async context => { // context.caller contains information about the current user, the defined // timezone, etc. // In this case, context.caller.email is the email used in Forest by the user // that initiated the call const isAllowed = await myFunctionToCheckIfUserIsAllowed(context.caller.email); if (!isAllowed) { // Raising an error here will prevent the execution of the update function, // as well as any other hooks that may be defined afterwards. context.throwForbiddenError(`${context.caller.email} is not allowed!`); }});
Another good example: each time a new User is created in the database, send them a welcome email.
user.addHook('After', 'Create', async (context, responseBuilder) => { // The result of the create function always returns an array of records const userEmail = context.records[0]?.email; await MyEmailSender.sendEmail({ from: 'erlich@bachman.com', to: userEmail, message: 'Hey, a new account was created with this email.', });});
Collection overrides provide the ability to completely replace the default behavior of CUD operations. This feature allows for custom implementations of create, update, and delete operations, giving you full control over data handling.
Collection overrides allow you to define custom behavior that will entirely replace the default implementation of the create, update, and delete operations.To define an override for a Collection, you must specify the handler function that will be executed instead of the default operation. The custom handler function will receive a context object containing relevant information for the operation.
Unknown properties in returned records will be removed.
collection.overrideCreate(async context => { // Custom logic to handle creation // context.data contains the data intended for creation // Return an array of created records});
collection.overrideUpdate(async context => { // Custom logic to handle update // context.filter to determine which records are targeted // context.patch contains the data for update // Perform update operation});
collection.overrideDelete(async context => { // Custom logic to handle deletion // context.filter to determine which records are targeted // Perform deletion operation});
Overrides take precedence over the default operation. Ensure your custom handlers properly manage all necessary logic for the operation, as the default behavior will not be executed.