Lifecycle hooks for intercepting and modifying request/response processing at various stages.
Add lifecycle hooks to intercept request/response processing.
/**
* Add a lifecycle hook
* @param hookName - Name of the lifecycle hook
* @param handler - Hook handler function
* @returns FastifyInstance for method chaining
*/
addHook(hookName: HookName, handler: HookHandler): FastifyInstance;type HookName =
| 'onRequest'
| 'preParsing'
| 'preValidation'
| 'preHandler'
| 'preSerialization'
| 'onSend'
| 'onResponse'
| 'onTimeout'
| 'onError'
| 'onClose'
| 'onReady'
| 'onListen'
| 'onRegister'
| 'onRequestAbort'
| 'preClose';First hook executed in the request lifecycle, before body parsing.
/**
* Called at the beginning of request processing
* @param request - Fastify request object (body is null)
* @param reply - Fastify reply object
* @param done - Callback function for completion
*/
type onRequestHookHandler = (
request: FastifyRequest,
reply: FastifyReply,
done: (err?: Error) => void
) => void;
/**
* Async version of onRequest hook
*/
type onRequestAsyncHookHandler = (
request: FastifyRequest,
reply: FastifyReply
) => Promise<void>;Usage Examples:
// Callback style
fastify.addHook('onRequest', (request, reply, done) => {
// Log incoming requests
console.log(`${request.method} ${request.url}`);
done();
});
// Async style
fastify.addHook('onRequest', async (request, reply) => {
// Add request ID
request.id = generateRequestId();
// Check rate limiting
const allowed = await checkRateLimit(request.ip);
if (!allowed) {
reply.code(429).send({ error: 'Too Many Requests' });
}
});Called before request body parsing, allows payload modification.
/**
* Called before body parsing, can modify payload
* @param request - Fastify request object
* @param reply - Fastify reply object
* @param payload - Request payload stream
* @param done - Callback with optional payload modification
*/
type preParsingHookHandler = (
request: FastifyRequest,
reply: FastifyReply,
payload: RequestPayload,
done: (err?: Error, payload?: RequestPayload) => void
) => void;
/**
* Async version of preParsing hook
*/
type preParsingAsyncHookHandler = (
request: FastifyRequest,
reply: FastifyReply,
payload: RequestPayload
) => Promise<RequestPayload | undefined>;Usage Examples:
// Callback style - modify payload
fastify.addHook('preParsing', (request, reply, payload, done) => {
// Decompress gzipped payload
if (request.headers['content-encoding'] === 'gzip') {
const gunzip = zlib.createGunzip();
done(null, payload.pipe(gunzip));
} else {
done();
}
});
// Async style - payload logging
fastify.addHook('preParsing', async (request, reply, payload) => {
if (request.headers['content-type']?.includes('application/json')) {
console.log('Processing JSON payload');
}
});Called after body parsing but before schema validation.
/**
* Called after parsing, before validation
* @param request - Fastify request object (body is parsed)
* @param reply - Fastify reply object
* @param done - Callback function for completion
*/
type preValidationHookHandler = (
request: FastifyRequest,
reply: FastifyReply,
done: (err?: Error) => void
) => void;
/**
* Async version of preValidation hook
*/
type preValidationAsyncHookHandler = (
request: FastifyRequest,
reply: FastifyReply
) => Promise<void>;Usage Examples:
// Input sanitization
fastify.addHook('preValidation', async (request, reply) => {
if (request.body && typeof request.body === 'object') {
// Sanitize string fields
for (const [key, value] of Object.entries(request.body)) {
if (typeof value === 'string') {
request.body[key] = value.trim();
}
}
}
});
// Authentication check
fastify.addHook('preValidation', async (request, reply) => {
const token = request.headers.authorization?.replace('Bearer ', '');
if (!token) {
reply.code(401).send({ error: 'Missing token' });
return;
}
try {
const user = await verifyToken(token);
request.user = user;
} catch (err) {
reply.code(401).send({ error: 'Invalid token' });
}
});Called after validation but before route handler execution.
/**
* Called after validation, before handler
* @param request - Fastify request object (validated)
* @param reply - Fastify reply object
* @param done - Callback function for completion
*/
type preHandlerHookHandler = (
request: FastifyRequest,
reply: FastifyReply,
done: (err?: Error) => void
) => void;
/**
* Async version of preHandler hook
*/
type preHandlerAsyncHookHandler = (
request: FastifyRequest,
reply: FastifyReply
) => Promise<void>;Usage Examples:
// Authorization check
fastify.addHook('preHandler', async (request, reply) => {
const requiredRole = request.routeConfig?.requiredRole;
if (requiredRole && (!request.user || !request.user.roles.includes(requiredRole))) {
reply.code(403).send({ error: 'Insufficient permissions' });
}
});
// Request context setup
fastify.addHook('preHandler', async (request, reply) => {
request.context = {
requestId: request.id,
userId: request.user?.id,
timestamp: Date.now()
};
});Called before response serialization, can modify payload.
/**
* Called before response serialization
* @param request - Fastify request object
* @param reply - Fastify reply object
* @param payload - Response payload to be serialized
* @param done - Callback with optional payload modification
*/
type preSerializationHookHandler = (
request: FastifyRequest,
reply: FastifyReply,
payload: any,
done: (err?: Error, payload?: any) => void
) => void;
/**
* Async version of preSerialization hook
*/
type preSerializationAsyncHookHandler = (
request: FastifyRequest,
reply: FastifyReply,
payload: any
) => Promise<any>;Usage Examples:
// Add metadata to all responses
fastify.addHook('preSerialization', async (request, reply, payload) => {
if (payload && typeof payload === 'object' && !Array.isArray(payload)) {
return {
...payload,
_metadata: {
requestId: request.id,
timestamp: new Date().toISOString(),
version: '1.0'
}
};
}
return payload;
});
// Response filtering based on user permissions
fastify.addHook('preSerialization', async (request, reply, payload) => {
if (request.user && payload?.sensitive_data) {
if (!request.user.roles.includes('admin')) {
delete payload.sensitive_data;
}
}
return payload;
});Called before sending the response, can modify the serialized payload.
/**
* Called before sending response
* @param request - Fastify request object
* @param reply - Fastify reply object
* @param payload - Serialized response payload
* @param done - Callback with optional payload modification
*/
type onSendHookHandler = (
request: FastifyRequest,
reply: FastifyReply,
payload: string | Buffer,
done: (err?: Error, payload?: string | Buffer) => void
) => void;
/**
* Async version of onSend hook
*/
type onSendAsyncHookHandler = (
request: FastifyRequest,
reply: FastifyReply,
payload: string | Buffer
) => Promise<string | Buffer>;Usage Examples:
// Compress responses
fastify.addHook('onSend', async (request, reply, payload) => {
if (request.headers['accept-encoding']?.includes('gzip') &&
payload.length > 1024) {
reply.header('content-encoding', 'gzip');
return zlib.gzipSync(payload);
}
return payload;
});
// Response logging
fastify.addHook('onSend', (request, reply, payload, done) => {
console.log(`Response ${reply.statusCode} for ${request.method} ${request.url}`);
done();
});Called after the response is sent to the client.
/**
* Called after response is sent
* @param request - Fastify request object
* @param reply - Fastify reply object
* @param done - Callback function for completion
*/
type onResponseHookHandler = (
request: FastifyRequest,
reply: FastifyReply,
done: (err?: Error) => void
) => void;
/**
* Async version of onResponse hook
*/
type onResponseAsyncHookHandler = (
request: FastifyRequest,
reply: FastifyReply
) => Promise<void>;Usage Examples:
// Access logging
fastify.addHook('onResponse', async (request, reply) => {
const responseTime = Date.now() - request.startTime;
console.log(`${request.method} ${request.url} ${reply.statusCode} ${responseTime}ms`);
});
// Metrics collection
fastify.addHook('onResponse', async (request, reply) => {
metrics.incrementCounter('http_requests_total', {
method: request.method,
status_code: reply.statusCode,
route: request.routerPath
});
metrics.recordHistogram('http_request_duration_seconds',
(Date.now() - request.startTime) / 1000
);
});Called when an error occurs during request processing.
/**
* Called when an error occurs
* @param request - Fastify request object
* @param reply - Fastify reply object
* @param error - Error that occurred
* @param done - Callback function for completion
*/
type onErrorHookHandler = (
request: FastifyRequest,
reply: FastifyReply,
error: FastifyError,
done: (err?: Error) => void
) => void;
/**
* Async version of onError hook
*/
type onErrorAsyncHookHandler = (
request: FastifyRequest,
reply: FastifyReply,
error: FastifyError
) => Promise<void>;Usage Examples:
// Error logging
fastify.addHook('onError', async (request, reply, error) => {
console.error(`Error in ${request.method} ${request.url}:`, error);
// Send error to monitoring service
await errorTracker.recordError(error, {
requestId: request.id,
userId: request.user?.id,
route: request.routerPath
});
});
// Custom error responses
fastify.addHook('onError', (request, reply, error, done) => {
if (error.code === 'FST_ERR_VALIDATION') {
reply.code(400).send({
error: 'Validation Error',
details: error.validation
});
}
done();
});Called when a request times out.
/**
* Called when request times out
* @param request - Fastify request object
* @param reply - Fastify reply object
* @param done - Callback function for completion
*/
type onTimeoutHookHandler = (
request: FastifyRequest,
reply: FastifyReply,
done: (err?: Error) => void
) => void;
/**
* Async version of onTimeout hook
*/
type onTimeoutAsyncHookHandler = (
request: FastifyRequest,
reply: FastifyReply
) => Promise<void>;Usage Examples:
// Timeout logging
fastify.addHook('onTimeout', async (request, reply) => {
console.warn(`Request timeout: ${request.method} ${request.url}`);
// Log slow requests for optimization
await logSlowRequest({
method: request.method,
url: request.url,
params: request.params,
query: request.query
});
});Called when a client aborts the request.
/**
* Called when request is aborted by client
* @param request - Fastify request object
* @param done - Callback function for completion
*/
type onRequestAbortHookHandler = (
request: FastifyRequest,
done: (err?: Error) => void
) => void;
/**
* Async version of onRequestAbort hook
*/
type onRequestAbortAsyncHookHandler = (
request: FastifyRequest
) => Promise<void>;Usage Examples:
// Cleanup on abort
fastify.addHook('onRequestAbort', async (request) => {
console.log(`Request aborted: ${request.method} ${request.url}`);
// Cancel any ongoing operations
if (request.operationId) {
await cancelOperation(request.operationId);
}
});Called when the application is ready and all plugins are loaded.
/**
* Called when application is ready
* @param done - Callback function for completion
*/
type onReadyHookHandler = (done: (err?: Error) => void) => void;
/**
* Async version of onReady hook
*/
type onReadyAsyncHookHandler = () => Promise<void>;Usage Examples:
// Database initialization
fastify.addHook('onReady', async () => {
await database.connect();
console.log('Database connected');
});
// Health check setup
fastify.addHook('onReady', async () => {
setInterval(async () => {
await performHealthCheck();
}, 30000);
});Called when the server starts listening.
/**
* Called when server starts listening
* @param done - Callback function for completion
*/
type onListenHookHandler = (done: (err?: Error) => void) => void;
/**
* Async version of onListen hook
*/
type onListenAsyncHookHandler = () => Promise<void>;Usage Examples:
// Startup notifications
fastify.addHook('onListen', async () => {
console.log('Server is now listening');
await notifyServiceRegistry();
});Called when the server is closing.
/**
* Called when server is closing
* @param instance - Fastify instance being closed
* @param done - Callback function for completion
*/
type onCloseHookHandler = (
instance: FastifyInstance,
done: (err?: Error) => void
) => void;
/**
* Async version of onClose hook
*/
type onCloseAsyncHookHandler = (instance: FastifyInstance) => Promise<void>;Usage Examples:
// Cleanup resources
fastify.addHook('onClose', async (instance) => {
await database.disconnect();
await redis.quit();
console.log('Resources cleaned up');
});Called before the server starts closing.
/**
* Called before server starts closing
* @param done - Callback function for completion
*/
type preCloseHookHandler = (done: (err?: Error) => void) => void;
/**
* Async version of preClose hook
*/
type preCloseAsyncHookHandler = () => Promise<void>;Called when a plugin is registered.
/**
* Called when a plugin is registered
* @param instance - FastifyInstance where plugin is being registered
* @param done - Callback function for completion
*/
type onRegisterHookHandler = (
instance: FastifyInstance,
done: (err?: Error) => void
) => void;Usage Examples:
// Track plugin registration
fastify.addHook('onRegister', (instance, done) => {
console.log(`Plugin registered: ${instance.pluginName}`);
done();
});