Hooks (Middleware)
Hooks or (Middleware functions) are executed before or after a route gets executed. We call them hooks because they are functions that get hooked
into the Execution Path of a route and because 'Middleware' is too long to write ๐.
Hooks are useful when a route might require some extra data like authorization, filters or some extra processing like body parsing, logging, etc...
Same as routes the first parameter is always the Call Context, the rest of parameters are remote parameters that get deserialized and validated before the hook gets executed.
Defining a Hook
Unlike routes we can't define a Hook using a simple function Handler
we must use a HookDef
object. This is so typescript can statically know which router entries are hooks and which ones are routes.
never assign a type to Hooks, instead always use the
satisfies
operator!import {CallContext, hook, Routes} from '@mionkit/router';
import {myApp} from './myApp';
const routes = {
// using the hook function to define a hook
logger: hook(
async (ctx: CallContext): Promise<void> => {
const hasErrors = ctx.request.internalErrors.length > 0;
if (hasErrors) await myApp.cloudLogs.error(ctx.path, ctx.request.internalErrors);
else myApp.cloudLogs.log(ctx.path, ctx.shared.me.name);
},
// ensures logger is executed even if there are errors in the route or other hooks
{runOnError: true}
),
// ... other routes and hooks
} satisfies Routes;
Header Hooks
For cases were we nee to send or receive data in http headers we can use a HeaderHookDef
. These hooks are limited to just one remote parameter besides the context and must be of type string
, number
or boolean
. No other kind of data is allowed in headers.
Soft Type Conversion is used for headers as those are always strings, this means strings like '1' , '0', 'true', 'false'
will be converted to boolean
and numeric strings like '5' , '100' will be converted to a number
.
Any header name
Authorization
or AUTHORIZATION
will match the Hook with header name authorization
.import {headersHook, Routes} from '@mionkit/router';
import {getAuthUser, isAuthorized} from 'MyAuth';
const routes = {
// using the headersHook function to define a hook
auth: headersHook('authorization', async (ctx, token: string): Promise<void> => {
const me = await getAuthUser(token);
if (!isAuthorized(me)) throw {code: 401, message: 'user is not authorized'};
ctx.shared.auth = {me}; // user is added to ctx to shared with other routes/hooks
}),
// ... other routes and hooks
} satisfies Routes;
Raw Hooks
In case we need to access the raw or native underlying request and response, we must use a RawHookDef
.
These are hooks that receive the CallContext
, RawRequest
, RawResponse
and RouterOptions
, but can't receive any remote parameters or return any data, raw Hooks
can only modify the CallContext and return or throw errors.
Raw Hooks are useful to extend the router's core functionality, i.e: The router internally uses a Raw Hook
to parse and stringify the JSON body.
import {rawHook, Routes} from '@mionkit/router';
import {IncomingMessage, ServerResponse} from 'http';
type HttpRequest = IncomingMessage & {body: any};
const routes = {
// using the rawHook function to define a hook
progress: rawHook(async (ctx, rawRequest: HttpRequest, rawResponse: ServerResponse): Promise<void> => {
return new Promise((resolve) => {
const maxTime = 1000;
const increment = 10;
let total = 0;
const intervale = setInterval(() => {
if (total >= maxTime) {
clearInterval(intervale);
resolve();
}
total += increment;
rawResponse.write(`\n${total}%`);
}, increment);
});
}),
// ... other routes and hooks
} satisfies Routes;
Force Run On Errors
When there is an error in a route or hook the rest of hooks are not executed unless runOnError
is set to true
.
This is useful for some hooks like a logger that needs to be executed after any other hook and log all the errors or request data.
const routes = {
// using the hook function to define a hook
logger: hook(
async (ctx: CallContext): Promise<void> => {
const hasErrors = ctx.request.internalErrors.length > 0;
if (hasErrors) await myApp.cloudLogs.error(ctx.path, ctx.request.internalErrors);
else myApp.cloudLogs.log(ctx.path, ctx.shared.me.name);
},
// ensures logger is executed even if there are errors in the route or other hooks
{runOnError: true}
),
// ... other routes and hooks
} satisfies Routes;
Type Reference
HookBase (HookOptions)
* Raw hook, used only to access raw request/response and modify the call context.
* Can not declare extra parameters.
*/
export type RawHookDef<H extends RawHookHandler = any> = Pick<
RawProcedure<H>,
'type' | 'handler' | 'runOnError' | 'canReturnData' | 'useSerialization' | 'useValidation'
>;
HookDef
/** Hook definition, a function that hooks into the execution path */
export type HookDef<H extends Handler = any> = Pick<HookProcedure<H>, 'type' | 'handler' | 'runOnError'>;
HeaderHookDef
export type HeaderHookDef<H extends HeaderHandler = any> = Pick<
HeaderProcedure<H>,
'type' | 'handler' | 'runOnError' | 'headerName'
>;
RawHookDef
export type RawHookDef<H extends RawHookHandler = any> = Pick<
RawProcedure<H>,
'type' | 'handler' | 'runOnError' | 'canReturnData' | 'useSerialization' | 'useValidation'
>;