Skip to content

Middleware

Middleware wraps every handler execution using a Koa-style onion model. Each middleware can run logic before and after the handler, catch errors, modify context, and control whether the handler runs at all.

Adding middleware

At runtime

synkro.use(async (ctx, next) => {
console.log(`[${ctx.eventType}] started`);
await next();
console.log(`[${ctx.eventType}] finished`);
});

At startup

Pass a middlewares array in the configuration:

const synkro = await Synkro.start({
transport: "in-memory",
middlewares: [timingMiddleware, errorLogMiddleware],
});

MiddlewareFunction type

type MiddlewareCtx<T = unknown> = HandlerCtx<T> & {
eventType: string;
};
type MiddlewareFunction = (
ctx: MiddlewareCtx,
next: () => Promise<void>,
) => Promise<void>;

MiddlewareCtx extends HandlerCtx with the eventType field, so middleware knows which event triggered the handler. The next function calls the next middleware in the chain, or the handler itself if there are no more middlewares.

Onion model

Middleware executes in registration order going in, and reverse order going out:

Request → Middleware A (before) → Middleware B (before) → Handler
Response ← Middleware A (after) ← Middleware B (after) ← Handler

Examples

Timing middleware

const timingMiddleware: MiddlewareFunction = async (ctx, next) => {
const start = performance.now();
await next();
const duration = performance.now() - start;
console.log(`[${ctx.eventType}] completed in ${duration.toFixed(2)}ms`);
};
synkro.use(timingMiddleware);

Error logging middleware

const errorLogMiddleware: MiddlewareFunction = async (ctx, next) => {
try {
await next();
} catch (error) {
console.error(`[${ctx.eventType}] handler failed:`, error);
throw error; // re-throw to let retry logic handle it
}
};
synkro.use(errorLogMiddleware);

Authentication middleware

const authMiddleware: MiddlewareFunction = async (ctx, next) => {
if (!ctx.payload?.userId) {
throw new Error("Unauthorized: missing userId");
}
await next();
};

Composition

Under the hood, Synkro composes middlewares using a composeMiddleware function (also exported for advanced use cases). Calling next() more than once in a single middleware throws an error.