Lifecycle hooks for intercepting and modifying request processing at various stages of the pipeline in @hapi/hapi.
Extensions allow you to hook into the request lifecycle at specific points to modify processing behavior.
/**
* Register request lifecycle extensions
* @param events - Extension point(s) to hook into
* @param method - Extension method or array of methods
* @param options - Extension options
*/
ext(events: ExtensionEvent | ExtensionEvent[] | ExtensionPoint[], method?: ExtensionMethod, options?: ExtensionOptions): void;
type ExtensionEvent =
| 'onPreStart' // Before server starts
| 'onPostStart' // After server starts
| 'onPreStop' // Before server stops
| 'onPostStop' // After server stops
| 'onRequest' // On incoming request
| 'onPreAuth' // Before authentication
| 'onCredentials' // After credential lookup
| 'onPostAuth' // After authentication
| 'onPreHandler' // Before route handler
| 'onPostHandler' // After route handler
| 'onPreResponse' // Before response processing
| 'onPostResponse'; // After response sent
interface ExtensionPoint {
/** Extension event */
type: ExtensionEvent;
/** Extension method */
method: ExtensionMethod;
/** Extension options */
options?: ExtensionOptions;
}
type ExtensionMethod = (request: Request, h: ResponseToolkit, err?: Error) => any;
interface ExtensionOptions {
/** Extension execution order */
before?: string | string[];
/** Extension execution order */
after?: string | string[];
/** Context binding */
bind?: object;
/** Sandbox isolation */
sandbox?: 'server' | 'plugin';
}Extensions that run during server startup and shutdown.
// Server lifecycle extension events:
// 'onPreStart' - Before server starts listening
// 'onPostStart' - After server starts listening
// 'onPreStop' - Before server stops
// 'onPostStop' - After server stopsUsage Examples:
// Database connection on server start
server.ext('onPreStart', async (server) => {
console.log('Connecting to database...');
await database.connect();
console.log('Database connected');
});
// Cleanup on server stop
server.ext('onPreStop', async (server) => {
console.log('Closing database connection...');
await database.disconnect();
console.log('Database disconnected');
});
// Log server status
server.ext('onPostStart', (server) => {
console.log(`Server started at: ${server.info.uri}`);
});
server.ext('onPostStop', (server) => {
console.log('Server stopped');
});Extensions that run during request processing with access to request and response toolkit.
// Request lifecycle extension events:
// 'onRequest' - First request processing step
// 'onPreAuth' - Before authentication
// 'onCredentials' - After credential lookup
// 'onPostAuth' - After authentication
// 'onPreHandler' - Before route handler execution
// 'onPostHandler' - After route handler execution
// 'onPreResponse' - Before response transmission
// 'onPostResponse'- After response sent (cleanup)Usage Examples:
// Request logging and timing
server.ext('onRequest', (request, h) => {
request.app.startTime = Date.now();
console.log(`${request.method.toUpperCase()} ${request.path} - Started`);
return h.continue;
});
// Add request ID
server.ext('onRequest', (request, h) => {
request.app.requestId = require('uuid').v4();
return h.continue;
});
// Security headers
server.ext('onPreResponse', (request, h) => {
const response = request.response;
if (response.isBoom) {
return h.continue;
}
response.header('X-Request-ID', request.app.requestId);
response.header('X-Content-Type-Options', 'nosniff');
response.header('X-Frame-Options', 'DENY');
response.header('X-XSS-Protection', '1; mode=block');
return h.continue;
});
// Response timing
server.ext('onPreResponse', (request, h) => {
const duration = Date.now() - request.app.startTime;
console.log(`${request.method.toUpperCase()} ${request.path} - ${duration}ms`);
return h.continue;
});Extensions specifically for authentication processing.
Usage Examples:
// Pre-authentication processing
server.ext('onPreAuth', (request, h) => {
// Log authentication attempts
if (request.headers.authorization) {
console.log(`Auth attempt for ${request.path}`);
}
return h.continue;
});
// Post-authentication processing
server.ext('onPostAuth', (request, h) => {
if (request.auth.isAuthenticated) {
// Log successful authentication
console.log(`User ${request.auth.credentials.user} authenticated`);
// Add user info to request
request.app.user = request.auth.credentials;
}
return h.continue;
});
// Credentials processing
server.ext('onCredentials', (request, h) => {
// Modify or enhance credentials
if (request.auth.credentials) {
request.auth.credentials.loginTime = Date.now();
}
return h.continue;
});Extensions around route handler execution.
Usage Examples:
// Pre-handler validation
server.ext('onPreHandler', (request, h) => {
// Additional validation logic
if (request.path.startsWith('/admin/') && !request.auth.credentials?.scope?.includes('admin')) {
return h.response({ error: 'Insufficient privileges' }).code(403).takeover();
}
return h.continue;
});
// Post-handler processing
server.ext('onPostHandler', (request, h) => {
const response = request.response;
// Modify response based on user preferences
if (request.auth.credentials?.preferences?.format === 'xml') {
// Convert JSON to XML (example)
response.type('application/xml');
}
return h.continue;
});Extensions for handling errors during request processing.
Usage Examples:
// Global error handling
server.ext('onPreResponse', (request, h) => {
const response = request.response;
if (response.isBoom) {
// Log error
console.error(`Error ${response.output.statusCode}: ${response.message}`);
// Custom error response format
const customError = {
error: {
statusCode: response.output.statusCode,
message: response.message,
requestId: request.app.requestId,
timestamp: new Date().toISOString()
}
};
return h.response(customError).code(response.output.statusCode);
}
return h.continue;
});Control the order of extension execution using before/after options.
Usage Examples:
// Extension that runs first
server.ext('onRequest', (request, h) => {
request.app.step1 = 'completed';
return h.continue;
}, {
before: '*' // Run before all other extensions
});
// Extension that runs after another
server.ext('onRequest', (request, h) => {
request.app.step2 = 'completed';
return h.continue;
}, {
after: 'step1-extension'
});
// Multiple ordering constraints
server.ext('onPreResponse', (request, h) => {
// Add final headers
return h.continue;
}, {
after: ['security-headers', 'cors-headers'],
before: 'response-compression'
});Extensions can also be defined at the route level.
interface RouteExtensions {
/** Route-level extensions */
[key: string]: ExtensionMethod | ExtensionMethod[];
}Usage Examples:
server.route({
method: 'GET',
path: '/special',
options: {
ext: {
onPreHandler: (request, h) => {
// Route-specific pre-handler logic
request.app.specialRoute = true;
return h.continue;
},
onPostHandler: [
(request, h) => {
// First post-handler
return h.continue;
},
(request, h) => {
// Second post-handler
return h.continue;
}
]
}
},
handler: (request, h) => {
return { special: request.app.specialRoute };
}
});Extensions can return different values to control request processing flow.
// Extension return values:
// h.continue - Continue normal processing
// h.close - Close connection without response
// h.abandon - Abandon request processing
// Response object - Override normal response
// Error/Boom - Return error response
// Promise - Async extension (await result)Usage Examples:
// Conditional processing
server.ext('onPreHandler', (request, h) => {
if (request.path === '/maintenance') {
return h.response({ message: 'Service under maintenance' })
.code(503)
.takeover();
}
return h.continue;
});
// Async extension
server.ext('onPreAuth', async (request, h) => {
// Rate limiting check
const isAllowed = await rateLimit.check(request.info.remoteAddress);
if (!isAllowed) {
return h.response({ error: 'Rate limit exceeded' })
.code(429)
.takeover();
}
return h.continue;
});
// Connection management
server.ext('onRequest', (request, h) => {
if (server.load.heapUsed > MAX_HEAP_SIZE) {
return h.close; // Close connection immediately
}
return h.continue;
});interface Request {
/** Application state modified by extensions */
app: object;
/** Plugin state modified by extensions */
plugins: object;
/** Request logs including extension logs */
logs: LogEntry[];
}
interface ResponseToolkit {
/** Continue normal processing */
continue: symbol;
/** Close connection */
close: symbol;
/** Abandon request processing */
abandon: symbol;
}