Middleware functions are defined using middleFn and can be executed before or after a route. middleware are functions that can be chained to create the full Execution Chain for a route.
middleFn are useful when a route might require some extra data like authorization, filters or some extra processing like body parsing, logging, etc...
The first parameter is always the Call Context, the rest of parameters are remote parameters that get deserialized and validated before the middleFn gets executed.
import {CallContext, middleFn, Routes} from '@mionkit/router';
import {myApp} from './full-example.app.ts';
const routes = {
// using the middleFn function to define a middleFn
logger: middleFn(
async (ctx: CallContext): Promise<void> => {
const hasErrors = ctx.request.thrownErrors && Object.keys(ctx.request.thrownErrors).length > 0;
if (hasErrors) await myApp.cloudLogs.error(ctx.path, ctx.request.thrownErrors);
else myApp.cloudLogs.log(ctx.path, ctx.shared.me.name);
},
// ensures logger is executed even if there are errors in the route or other middleFns
{runOnError: true}
),
// ... other routes and middleFns
} satisfies Routes;
For cases where we need to send or receive data in HTTP headers we use headersFn and HeadersSubset<T>. Unlike route/middleFn parameters (which are serialized to the request/response body), HeadersSubset values are serialized directly to HTTP headers.
Use headersFn with a HeadersSubset<T> parameter to read headers from the incoming request:
import {HeadersSubset, RpcError} from '@mionkit/core';
import {headersFn, middleFn, Routes} from '@mionkit/router';
import {getAuthUser, isAuthorized} from 'MyAuth';
const routes = {
// using the headersFn to declare request headers, headers param must be next after context
auth: headersFn(async (ctx, {headers}: HeadersSubset<'Authorization'>): Promise<void | RpcError<'not-authorized'>> => {
const token = headers.Authorization;
const me = await getAuthUser(token);
if (!isAuthorized(me)) {
return new RpcError({type: 'not-authorized', publicMessage: 'User is not authorized'});
}
ctx.shared.auth = {me}; // user is added to ctx to shared with other routes/middleFns
}),
// set response headers
serverName: middleFn((ctx): HeadersSubset<'Server'> => {
return new HeadersSubset({Server: 'my-server'});
}),
// ... other routes and middleFns
} satisfies Routes;
Authorization, AUTHORIZATION, and authorization all match the same header.Return a HeadersSubset from any middleFn to set headers in the HTTP response. This works with both middleFn and headersFn:
import {HeadersSubset, RpcError} from '@mionkit/core';
import {headersFn, middleFn, Routes} from '@mionkit/router';
import {getAuthUser, isAuthorized} from 'MyAuth';
const routes = {
// using the headersFn to declare request headers, headers param must be next after context
auth: headersFn(async (ctx, {headers}: HeadersSubset<'Authorization'>): Promise<void | RpcError<'not-authorized'>> => {
const token = headers.Authorization;
const me = await getAuthUser(token);
if (!isAuthorized(me)) {
return new RpcError({type: 'not-authorized', publicMessage: 'User is not authorized'});
}
ctx.shared.auth = {me}; // user is added to ctx to shared with other routes/middleFns
}),
// set response headers
serverName: middleFn((ctx): HeadersSubset<'Server'> => {
return new HeadersSubset({Server: 'my-server'});
}),
// ... other routes and middleFns
} satisfies Routes;
Use two type parameters to declare required and optional headers:
import {HeadersSubset} from '@mionkit/core';
import {headersFn} from '@mionkit/router';
// HeadersSubset<RequiredHeaders, OptionalHeaders>
// - First type parameter: required headers (must be present)
// - Second type parameter: optional headers (may or may not be present)
// Example: Authorization is required, User-Agent is optional
const authWithOptionalAgent = headersFn(async (ctx, {headers}: HeadersSubset<'Authorization', 'User-Agent'>): Promise<void> => {
// headers.Authorization is guaranteed to exist (required)
const token = headers.Authorization;
// headers['User-Agent'] may be undefined (optional)
const userAgent = headers['User-Agent'];
console.log(`Token: ${token}, Agent: ${userAgent ?? 'unknown'}`);
});
// Multiple required and optional headers
const multiHeadersFn = headersFn(
async (
ctx,
{headers}: HeadersSubset<'Authorization' | 'Content-Type', 'X-Request-Id' | 'X-Correlation-Id'>
): Promise<void> => {
// Required headers - always present
const auth = headers.Authorization;
const contentType = headers['Content-Type'];
// Optional headers - may be undefined
const requestId = headers['X-Request-Id'];
const correlationId = headers['X-Correlation-Id'];
console.log(`Auth: ${auth}, ContentType: ${contentType}`);
console.log(`RequestId: ${requestId ?? 'none'}, CorrelationId: ${correlationId ?? 'none'}`);
}
);
export {authWithOptionalAgent, multiHeadersFn};
string types only. Complex data types cannot be serialized to HTTP headers.In case we need to access the raw or native underlying request and response, we must use a rawMiddleFn.
These are functions that receive the CallContext, RawRequest, RawResponse and RouterOptions, but can't receive any remote parameters or return any data.
Raw Middleware functions can only modify the CallContext and return or throw errors.
Raw Middleware functions are useful to extend the router's core functionality, i.e: The router internally uses them for serialization/deserialization.
import {rawMiddleFn, Routes} from '@mionkit/router';
import {IncomingMessage, ServerResponse} from 'http';
type HttpRequest = IncomingMessage & {body: any};
const routes = {
// using the rawMiddleFn function to define a middleFn
progress: rawMiddleFn(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 middleFns
} satisfies Routes;
When there is an error in a route or middleFn the rest of middleFns are not executed unless runOnError is set to true.
This is useful for some middleFns like a logger that needs to be executed after any other middleFn and log all the errors or request data.
import {CallContext, middleFn, Routes} from '@mionkit/router';
import {myApp} from './full-example.app.ts';
const routes = {
// using the middleFn function to define a middleFn
logger: middleFn(
async (ctx: CallContext): Promise<void> => {
const hasErrors = ctx.request.thrownErrors && Object.keys(ctx.request.thrownErrors).length > 0;
if (hasErrors) await myApp.cloudLogs.error(ctx.path, ctx.request.thrownErrors);
else myApp.cloudLogs.log(ctx.path, ctx.shared.me.name);
},
// ensures logger is executed even if there are errors in the route or other middleFns
{runOnError: true}
),
// ... other routes and middleFns
} satisfies Routes;
The Execution Chain is a list of all the middleFns and the route that are executed during a route call. That is: all middleFns before the route, the route itself, and all middleFns after the route. The Execution chain is generated when the the router is started so it is the same for all requests.
For every incoming request the Execution Chain is retrieved and all middleFns and the route get executed in order, there are no paths or conditionals everything is executed unless an error occurs.
import {Routes, initMionRouter, middleFn, route} from '@mionkit/router';
const routes = {
authorizationMiddleFn: middleFn((): void => undefined), // middleFn
users: {
userOnlyMiddleFn: middleFn((): void => undefined), // scoped middleFn
getUser: route((): null => null), // route
setUser: route((): null => null), // route
},
pets: {
getPet: route((): null => null), // route
setPet: route((): null => null), // route
},
errorHandlerMiddleFn: middleFn((): void => undefined), // middleFn
loggingMiddleFn: middleFn((): void => undefined), // middleFn
} satisfies Routes;
export const myValidApi = await initMionRouter(routes);
Generated Execution Chain for: pets.getPet
We can limit middleFns to be executed only on a subset of routes.
The userOnlyMiddleFn from previous example will be executed only for the routes under users but not for routes under pets.
Generated Execution Chain for: users.getUser