Create rest API quickly with Axe API framework: Part 5

Pavel Salauyou
6 min readOct 10, 2023

--

In the previous article, we took a detailed look at data queries and also made a simple hook for password encryption. In this article we will look at hooks and events, namely how to define them and how to distinguish between them.

Hooks

Hooks and events are essential components of the Axe API. They let you add your application logic to the API in a smart and easy-to-test way. In this section, we will explore how this logic works. Hooks and events are just functions that are executed at a certain point in time of an http request in the API.

In the previous article we wrote a small hook that encrypts the password, you have probably already understood the essence of its work, if not let’s take a closer look. Let’s start with the basic function of the hook that does nothing:

import { IHookParameter } from 'axe-api';

export default async (context: IHookParameter) => {
// add your custom logic in here
};

As you can tell, it’s a very basic function. You can add hook/event functions to your API and connect them with models in the Axe API way. Once they are linked, Axe API uses your functions during the process of handling HTTP requests and responses.

Events

In terms of implementation, there are almost no differences between events and hooks.

Axe api calls hooks and events sequentially and the only difference between them is that hooks are called synchronously and events are called asynchronously. Here is an example to better understand how an http request is handled in axe api:

const processHTTPRequest = async () => {
// doing some stuffs

// Setting the hook parameters
const hookParameters = {};

// Wait till the end
await callHooks(hookParameters);

// DO NOT wait the function
callEvents(hookParameters);

// doing some stuffs
};
  • await callHooks - call hooks
  • callEvents(hookParameters); - call events

Simply said axe api waits for called hooks to complete and does not wait for called events to complete.

Auto-discovering & Naming

Axe API executes hooks and events by their names and location. Here is an excellent illustration of how axe api finds hooks and events in the file system:

API Version — this indicates that your function will execute in the specified version of the API.

Type: type means that your function will be executed as a Hook or Event. You can place your function in the Hooks or Events folder.

Model Name: the model name represents what your function will work in which model. The model name must exactly match the model name.

Timing: timing means when your function will be executed. There are two available options that you can use:

  • onBefore - on the before the action has been executed
  • onAfter - On the after the action has been executed

Action Type: the action type describes that in which action your function is related. The possible options are:

  • Insert: Inserting a new record
  • UpdateQuery: The fetching-data that would be updated action
  • Update: The action of data update
  • DeleteQuery: The fetching-data that would be delete action
  • Delete: The action of data delete
  • ForceDeleteQuery: The fetching-data that would be force-delete action
  • ForceDelete: The action of data force-delete
  • Paginate: The action of data paginate
  • All: The action of data fetching all
  • Show: The action of fetching one item

Let’s test some hooks on a simple example let’s create a table and a statistics model and record there the data of the current number of users and orders. When creating and deleting users or orders, will recalculate the number of records in the tables of these models and save in the statistics table. Thus at any time we will be able to get up-to-date data on statistics without performing queries in other tables. It turns out that the statistics table is a kind of cache.

Let’s start by generating a new migration for statistics:

knex --esm migrate:make 5Statistics

Further we define the fields of the table:

migrations/<timeprefix>_Statistics.js

/**
* @param { import("knex").Knex } knex
* @returns { Promise<void> }
*/
exports.up = function(knex) {
return knex.schema.createTable('statistics', function (table) {
table.increments();
table.string('name').notNullable();
table.string('uid').notNullable().unique().index();
table.integer('total').notNullable();
table.timestamps();
});
};

/**
* @param { import("knex").Knex } knex
* @returns { Promise<void> }
*/
exports.down = function(knex) {
return knex.schema.dropTable('statistics');
};

name - just a textual description of the setting, for example, the Total Users.

uid - unique setting id, for example, totalUsers.

total - total number of table entries.

Run the migration:

knex --esm migrate:latest

If there were no errors, then everything went well and a new table with the above fields was created in the database. Now we need to create a statistics model:

app/v1/Models/Statistics.ts

import { Model } from 'axe-api';

class Statistics extends Model {
get fillable() {
return ['name', 'uid', 'total'];
}

get validations() {
return {
name: 'required|min:3|max:50',
uid: 'required',
total: 'required'
};
}
}

export default Statistics;

Run the axe api server:

npm run start:dev

Go to the new route http://localhost:3000/api/v1/statistics and see:

{
"data": [],
"pagination": {
"total": 0,
"lastPage": 0,
"prevPage": null,
"nextPage": null,
"perPage": 10,
"currentPage": 1,
"from": 0,
"to": 0
}
}

As we can see axe api has successfully processed the model and created a new route statistics.

Now we have records in tables users and orders, but no statistics for these records. We can add them through the newly created api for statistics.

Run a post query to the api to add statistics for the users table:

curl \
-d '{"name": "Total Users", "uid": "totalUsers", "total": 2}' \
-H "Content-Type: application/json" \
-X POST http://localhost:3000/api/v1/statistics

Then, run a post query to the api to add statistics for the orders table:

curl \
-d '{"name": "Total Orders", "uid": "totalOrders", "total": 2}' \
-H "Content-Type: application/json" \
-X POST http://localhost:3000/api/v1/statistics

Сheck if records have been added to the table, http://localhost:3000/api/v1/statistics:

{
"data": [
{
"id": 1,
"name": "Total Users",
"uid": "totalUsers",
"total": 2,
"created_at": "2023-07-26T18:25:11.000Z",
"updated_at": "2023-07-26T18:25:11.000Z"
},
{
"id": 2,
"name": "Total Orders",
"uid": "totalOrders",
"total": 2,
"created_at": "2023-07-26T18:27:23.000Z",
"updated_at": "2023-07-26T18:27:23.000Z"
}
],
"pagination": {
"total": 2,
"lastPage": 1,
"prevPage": null,
"nextPage": null,
"perPage": 10,
"currentPage": 1,
"from": 0,
"to": 2
}
}

As you can see in the result there are 2 records that display the current statistics in the application, it can be displayed for example on the dashboard or used for analytics.

Now let’s move on to the hooks that will update the data in the database, but we will update the statistics not through the api, but directly through the knex library.

Create a hook file for the User model:

app/v1/Hooks/User/onAfterInsert.ts

import { IHookParameter } from 'axe-api';

export default async ({ database }: IHookParameter) => {
const total = await database('users').count({ count: '*' }).first();
await database('statistics')
.update({ total: total?.count || 0 })
.where({ uid: 'totalUsers' })
;
};

Add a new user:

curl \
-d '{"email": "james.castillo@example.com", "firstName": "James", "lastName":"Castillo", "password": "my-secret-password"}' \
-H "Content-Type: application/json" \
-X POST http://localhost:3000/api/v1/users

Сheck if the statistics have been updated, http://localhost:3000/api/v1/statistics:

// ...
{
"id": 1,
"name": "Total Users",
"uid": "totalUsers",
"total": 3,
"created_at": "2023-07-26T18:25:11.000Z",
"updated_at": "2023-07-26T18:25:11.000Z"
}
// ...

As you can see the total field has changed to 3. Let’s add a user deletion hook, basically we’ll do the same thing there. Just copy onAfterInsert.ts and rename to onAfterDelete.ts.

Perform a deletion request:

curl \
-H "Content-Type: application/json" \
-X DELETE http://localhost:3000/api/v1/users/3

Now let’s do the same for the Order model, create a hook file for the Order model. Copy the files app/v1/Hooks/User/onAfterInsert.ts , app/v1/Hooks/User/onAfterInsert.ts into the directory app/v1/Hooks/Order and change the contents to:

import { IHookParameter } from 'axe-api';

export default async ({ database }: IHookParameter) => {
const total = await database('orders').count({ count: '*' }).first();
await database('statistics')
.update({ total: total?.count || 0 })
.where({ uid: 'totalOrders' })
;
};

Hooks for Order work similarly to hooks for User, there are no differences, so we will not repeat with requests to the order api, do it yourself.

Conclusion

So in this article we have considered hooks and events, learnt that they are essentially the same thing, tested hooks onAfterInsert, onAfterDelete on a real example, and made sure that they really work as stated in axe api.

Axe api supports more hooks you can test them yourself using the code we wrote in this and previous articles as a basis. here is also a link to the github repository.

--

--