Hooks
Control request and response lifecycle with Thunder's powerful pre and post hook system.
Lifecycle Overview
Hooks are one of Thunder's most powerful features. They give you precise, declarative control over the request/response lifecycle - without tangling middleware logic into your business code.
Every hook in Thunder is composed of three concerns:
- Priority - determines the execution order relative to other hooks.
preblock - runs before the route handler executes.postblock - runs after the route handler produces a response.
Hook Priority
Priority controls the order in which hooks execute relative to one another. Thunder supports any numeric priority value.
| Priority Value | Execution Order |
|---|---|
1 | First - runs before hooks with higher numbers (e.g. security checks) |
2, 3 ... | Ascending order - higher numbers run later |
100 | Last - runs after all other hooks have completed (e.g. cleanup) |
Use 1 for hooks that must always run first, such as security or
authentication checks. Use a high number like 100 for cleanup or final
logging hooks that should run after everything else.
Pre Hooks
A pre hook executes before the route handler is invoked. This is the ideal place for:
- Authentication and authorization checks
- Request validation
- Header injection or modification
- Rate limiting enforcement
export const authHook = {
priority: 1,
pre: async (ctx: { req: Request; scope: any; name: string }) => {
const token = ctx.req.headers.get("Authorization");
if (!token) {
return Response.json({ error: "Unauthorized" }, { status: 401 });
}
// Token is valid - continue to handler
},
};Post Hooks
A post hook executes after the route handler has returned a response. This is the ideal place for:
- Request/response logging
- Analytics and telemetry
- Executing side effects (e.g. sending emails, invalidating cache)
import { Logger } from "@/core/utils/logger.ts";
export const loggingHook = {
priority: 100,
post: async (ctx: {
req: Request;
res: Response;
scope: any;
name: string;
}) => {
Logger.info(
`[${ctx.req.method}] ${ctx.req.url} → ${ctx.res.status} (Hook: ${ctx.name})`,
);
},
};Using Both Pre and Post
A single hook definition can include both a pre and a post block:
import { Logger } from "@/core/utils/logger.ts";
export const timingHook = {
priority: 2,
pre: async (ctx: { req: Request; scope: any; name: string }) => {
// Attach a start timestamp to the request scope
ctx.scope._startTime = Date.now();
},
post: async (ctx: {
req: Request;
res: Response;
scope: any;
name: string;
}) => {
const duration = Date.now() - ctx.scope._startTime;
Logger.info(`Request completed in ${duration}ms`);
},
};Priority Examples
// Runs before everything else
export const securityHook = {
priority: 1,
pre: async (ctx) => {
// Block suspicious requests
},
};// Hook A runs before Hook B
export const hookA = { priority: 2, pre: async (ctx) => { /* ... */ } };
export const hookB = { priority: 3, pre: async (ctx) => { /* ... */ } };// Runs after all other hooks complete
export const cleanupHook = {
priority: 100,
post: async (ctx) => {
// Final cleanup or audit logging
},
};Hooks integrate seamlessly with Thunder's validation system. If a pre hook
returns a Response, the route handler is skipped entirely and that
response is sent directly to the client - making hooks an elegant mechanism
for short-circuiting invalid requests.