# Pre Hooks

> Run your TypeScript before any API of API Maker - at the level of an instance, a database, a table, one API, a custom, system or third party API, or a group - to check, change, narrow or answer a request, in the order API Maker runs them.

Source: https://docs.apimaker.dev/v1/docs/apis-all/hooks/preHook-api.html

A pre hook is a TypeScript function of yours which runs **before** an API. It sees the request, can change it, can answer instead of the API, and can refuse it. Attach it to a whole instance, a database, a table, one API, a custom API, a system API or a third party API, and change it any time without a restart.

> Diagram : Hooks around an API : pre hooks from the instance down to the API, the API, then post hooks from the API up to the instance
>
> Order of the hooks around a database API : the pre hooks of the instance, then of the database, then of the table, then of the API run before it ; the API runs ; the post hooks run in the opposite order, API, table, database, instance. A pre hook which returns a value answers the caller and stops everything after it.

## Where hooks live

| Level | Runs for | Where |
|---|---|---|
| Instance | every API of every table of the instance | Instances page › the instance › hooks |
| Database | every API of every table of the database | the database › hooks |
| Table (collection) | every API of the table | the table › hooks |
| API | one API of one table, `SCHEMA_GET_ALL` for example | the API › hooks |
| Custom, system, third party API | that API | its hooks tab |
| Group | the hooks above, only for API users of the groups named on the hook | `groupNames` on the hook |

- Pre hooks run from the widest level to the narrowest : instance, database, table, API, each level in the order of its list. Post hooks run the other way round. See [Post hooks](https://docs.apimaker.dev/v1/docs/apis-all/hooks/postHook-api.html).
- A hook with `groupNames` runs only when the API user of the call belongs to one of those groups. That is how one table gets a row scoping hook for the application users and none for the back office.

## The code

```typescript
import * as T from 'types';
import * as db from 'db-interfaces';

async function main(g: T.IAMGlobal) {
    if (!g.req.isApiRequestFromUser) return; // calls made by your own code : leave them alone

    // g.req.query, g.req.body, g.req.params, g.req.headers : the request, changeable
    // g.req.auth.authAMUser, g.req.auth.authAMDB… : who calls
    // g.shared : values for the post hooks of the same request
}
module.exports = main;
```

What the return value does :

| The hook | Effect |
|---|---|
| returns nothing | The next hook, then the API, run. |
| returns a value | That value is the answer of the request : the remaining hooks and the API **do not run**. The response is `200` with the value in `data`. |
| sets `g.res.output` and returns nothing | The API runs ; its output replaces `g.res.output` unless a post hook changes it again. |
| throws | The request stops with the error : `throw new Error('…')` gives `500` and the message, or throw an `IResponseError[]` with a `code` for another status. |

- Every hook runs in the sandbox (or on the native process with `runOnNativeProcess`), within the sandbox timeout of the call.
- When the answer of an API comes from the [cache](https://docs.apimaker.dev/v1/docs/features/automatic-caching.html), neither pre nor post hooks run.
- Calls from your own code (`g.sys.db.getAll` in a custom API) skip the hooks by default. Pass `skipHookRunning: false` to run them.

## Narrow a request to the rows of the caller

The most useful pre hook : a filter added to every read and a stamp on every write, so a person only reaches their own rows.

**Collection level pre hook on orders**

```typescript
import * as T from 'types';

async function main(g: T.IAMGlobal) {
    if (!g.req.isApiRequestFromUser) return;
    const person = g.req.auth.authAMDB;               // the person, from x-am-user-authorization
    if (!person) throw new Error('Sign in first.');
    const mine = { customer_id: person.customer_id };

    const api = g.req.reqInfo.apiInfo.id;              // SCHEMA_GET_ALL, SCHEMA_POST_QUERY, …
    if (api.endsWith('_GET_ALL') || api.endsWith('_GET_ALL_STREAM') || api.endsWith('_GET_BY_ID') || api.endsWith('_DEL_DELETE_BY_ID') || api.endsWith('_PUT_UPDATE_BY_ID')) {
        // filters travel in query.find on these APIs
        const find = g.req.query.find ? (typeof g.req.query.find === 'string' ? JSON.parse(g.req.query.find) : g.req.query.find) : {};
        g.req.query.find = { $and: [ find, mine ] };
    } else if (api.endsWith('_POST_QUERY') || api.endsWith('_POST_QUERY_STREAM') || api.endsWith('_POST_COUNT') || api.endsWith('_UPDATE_MANY') || api.endsWith('_POST_QUERY_DELETE') || api.endsWith('_POST_DISTINCT_QUERY')) {
        g.req.body.find = { $and: [ g.req.body.find || {}, mine ] };
    } else if (api.endsWith('_POST_BULK_INSERT') || api.endsWith('_MASTER_SAVE')) {
        const rows = Array.isArray(g.req.body) ? g.req.body : [ g.req.body ];
        for (const row of rows) row.customer_id = person.customer_id;   // stamp the owner
    } else if (api.endsWith('_POST_AGGREGATE') || api.endsWith('_GET_DISTINCT')) {
        throw new Error('Not allowed on this table.');
    }
}
module.exports = main;
```

The [APIs Security Report](https://docs.apimaker.dev/v1/docs/apis-security/api-security-report.html) writes this hook for you, for the tables which need it, and [Handle role based permissions](https://docs.apimaker.dev/v1/docs/authorization/handle-role-based-permissions.html) explains the pattern step by step.

## Answer without the API

**A pre hook which answers from the cache of the process**

```typescript
async function main(g: T.IAMGlobal) {
    const cached = await g.sys.cache.getKey('countries');
    if (cached) return JSON.parse(cached);   // the API does not run
}
module.exports = main;
```

## Validate and change

```typescript
async function main(g: T.IAMGlobal) {
    if (!g.req.isApiRequestFromUser) return;
    if (g.req.body?.price_cents < 0) throw new Error('A price can not be negative.');
    g.req.body.updated_by = g.req.auth.authAMDB?.email;
    g.req.headers['x-am-response-case'] = 'camelCase';   // headers can change too
    g.shared.startedAt = Date.now();                     // for the post hook
}
module.exports = main;
```

## Utility classes in a hook

```typescript
import * as T from 'types';
import * as Guards from 'utils/Guards';

async function main(g: T.IAMGlobal) {
    Guards.requireRole(g, 'PURCHASE_INVOICE_VIEW');
}
module.exports = main;
```

- Press Ctrl+Space in the editor for the list of your [utility classes](https://docs.apimaker.dev/v1/docs/utility-class/utility-class.html).

## Testing a hook

- The API testing page runs an API with its hooks. While you edit a hook there, the **Test** button of the hook runs only that hook, so you see its effect alone.
- Every hook has versions : keep the old one active until the new one is ready.
- The log profile records each hook run with the request ; the log explorer shows them next to the API call.

## Good to know

- Streams : pre hooks run before the stream starts ; post hooks can not change what was streamed.
- A hook which calls the same API it is attached to creates a cycle : API Maker detects it and stops the request with an error.
- Hooks are files in Git, under the folder of their instance, database, table or API, and deploy with a Git pull.

- [Post hooks →](https://docs.apimaker.dev/v1/docs/apis-all/hooks/postHook-api.html)
- [The global object g](https://docs.apimaker.dev/v1/docs/pre-defined-terms/global-object-g.html)
