Pluggable route handler that manages the complete request lifecycle with hooks for preprocessing, processing, and post-processing phases.
import Route from '@stackpress/ingest/Route';
// Static usage
const success = await Route.emit(
'user-login',
request,
response,
server
);
// Instance usage
const route = new Route(action, request, response, server);
const success = await route.emit();- Static Methods
- Properties
- Methods
- Route Lifecycle
- Error Handling
- Integration with Server
- Best Practices
- Examples
The following methods can be accessed directly from Route itself.
The following example shows how to emit route events with complete lifecycle management.
const success = await Route.emit(
async (req, res, ctx) => {
const user = await ctx.resolve('get-user', req);
res.setResults(user);
},
request,
response,
server
);
// Or with string event
const success = await Route.emit(
'user-profile',
request,
response,
server
);Parameters
| Parameter | Type | Description |
|---|---|---|
event |
ServerAction<C, R, S>|string |
Route action function or event name |
request |
Request<R> |
Request object |
response |
Response<S> |
Response object |
context |
Server<C, R, S> |
Server context |
Returns
A promise that resolves to true if the route completed successfully, false if aborted.
The following properties are available when instantiating a Route.
| Property | Type | Description |
|---|---|---|
event |
ServerAction<C, R, S>|string |
The route action or event name |
request |
Request<R> |
Request object (readonly) |
response |
Response<S> |
Response object (readonly) |
context |
Server<C, R, S> |
Server context (readonly) |
The following methods are available when instantiating a Route.
The following example shows how to execute the complete route lifecycle.
const route = new Route(
async (req, res, ctx) => {
res.setJSON({ message: 'Hello World' });
},
request,
response,
server
);
const success = await route.emit();
if (success) {
console.log('Route completed successfully');
} else {
console.log('Route was aborted');
}Returns
A promise that resolves to true if all lifecycle phases completed, false if any phase was aborted.
The following example shows how to run the request preparation phase.
const success = await route.prepare();
// Emits 'request' event and handles any errorsReturns
A promise that resolves to true if preparation succeeded, false if aborted.
The following example shows how to execute the main route processing.
const success = await route.process();
// Executes the route action and handles errors/404sReturns
A promise that resolves to true if processing succeeded, false if aborted.
The following example shows how to run the response finalization phase.
const success = await route.shutdown();
// Emits 'response' event and handles any errorsReturns
A promise that resolves to true if shutdown succeeded, false if aborted.
The Route class manages a three-phase lifecycle for request processing with comprehensive error handling and abort capabilities.
The preparation phase handles authentication, logging, request validation, and other preprocessing tasks.
// Emits 'request' event for preprocessing
await server.emit('request', request, response);
// Example request preprocessor
server.on('request', async (req, res) => {
// Authentication
if (!req.headers.authorization) {
res.setError('Unauthorized', {}, [], 401);
return false; // Abort processing
}
// Request logging
console.log(`${req.method} ${req.url.pathname}`);
return true; // Continue processing
});The processing phase executes the main route logic and handles 404 errors for unhandled routes.
// Executes the route action
if (typeof event === 'string') {
await server.emit(event, request, response);
} else {
await event(request, response, server);
}
// Handles 404 if no response body or status code
if (!response.body && !response.code) {
response.setError('Not Found', {}, [], 404);
await server.emit('error', request, response);
}The shutdown phase handles response headers, logging, cleanup, and other postprocessing tasks.
// Emits 'response' event for postprocessing
await server.emit('response', request, response);
// Example response postprocessor
server.on('response', async (req, res) => {
// Add security headers
res.headers.set('X-Frame-Options', 'DENY');
res.headers.set('X-Content-Type-Options', 'nosniff');
// Response logging
console.log(`Response: ${res.code} ${res.status}`);
return true; // Continue processing
});Route provides comprehensive error handling throughout the lifecycle with automatic error conversion and abort capabilities.
The Route class automatically converts thrown errors into proper exception responses.
try {
await routeAction(request, response, server);
} catch (error) {
// Automatically converts errors to exceptions
const exception = Exception.upgrade(error).toResponse();
response.setError(exception);
// Allows plugins to handle the error
await server.emit('error', request, response);
}Custom error handling can be implemented through error event listeners.
server.on('error', async (req, res) => {
// Custom error handling
if (res.code === 404) {
res.setHTML('<h1>Page Not Found</h1>');
} else if (res.code >= 500) {
// Log server errors
console.error('Server error:', res.error);
res.setHTML('<h1>Internal Server Error</h1>');
}
return true; // Continue processing
});Any lifecycle phase can abort processing by returning false or throwing an error.
server.on('request', async (req, res) => {
if (req.url.pathname.startsWith('/admin') && !isAdmin(req)) {
res.setError('Forbidden', {}, [], 403);
return false; // Abort - skip processing and shutdown
}
return true; // Continue to processing phase
});
function isAdmin(req: any) {
// Admin check logic
return req.headers.get('x-admin-token') === 'admin-secret';
}Route is typically used internally by the Server class but can be used directly for advanced use cases.
Use Route directly for custom request handling scenarios.
import Route from '@stackpress/ingest/Route';
import { server } from '@stackpress/ingest/http';
const app = server();
const req = app.request({ url: '/api/users' });
const res = app.response();
// Direct route execution
const success = await Route.emit(
async (request, response, context) => {
const users = await getUsers();
response.setResults(users);
},
req,
res,
app
);
async function getUsers() {
// User retrieval logic
return [{ id: 1, name: 'John' }, { id: 2, name: 'Jane' }];
}The Server class automatically uses Route for request handling with simplified syntax.
// Server automatically uses Route for request handling
app.get('/users', async (req, res, ctx) => {
// This action is wrapped in Route.emit() automatically
const users = await getUsers();
res.setResults(users);
});
// Equivalent to:
app.on('GET /users', async (req, res) => {
await Route.emit(
async (request, response, context) => {
const users = await getUsers();
response.setResults(users);
},
req,
res,
app
);
});The following best practices help organize route lifecycle management and error handling effectively.
Group related functionality in lifecycle hooks for better maintainability.
// Group related functionality in lifecycle hooks
server.on('request', async (req, res) => {
// Authentication and authorization
await authenticateUser(req, res);
await authorizeRequest(req, res);
// Request preprocessing
await parseRequestData(req);
await validateRequest(req, res);
return true;
});
server.on('response', async (req, res) => {
// Security headers
addSecurityHeaders(res);
// Response formatting
await formatResponse(res);
// Logging and metrics
logRequest(req, res);
recordMetrics(req, res);
return true;
});
async function authenticateUser(req: any, res: any) {
// Authentication logic
}
async function authorizeRequest(req: any, res: any) {
// Authorization logic
}
async function parseRequestData(req: any) {
// Request parsing logic
}
async function validateRequest(req: any, res: any) {
// Request validation logic
}
function addSecurityHeaders(res: any) {
// Security headers logic
}
async function formatResponse(res: any) {
// Response formatting logic
}
function logRequest(req: any, res: any) {
// Logging logic
}
function recordMetrics(req: any, res: any) {
// Metrics recording logic
}Implement comprehensive error recovery strategies for robust applications.
server.on('error', async (req, res) => {
// Log the error
logger.error('Request error:', {
url: req.url.href,
method: req.method,
error: res.error,
stack: res.stack
});
// Provide user-friendly error responses
if (res.code === 404) {
res.setHTML(await renderErrorPage('404'));
} else if (res.code >= 500) {
res.setHTML(await renderErrorPage('500'));
}
return true;
});
const logger = {
error: (message: string, data: any) => {
console.error(message, data);
}
};
async function renderErrorPage(type: string) {
// Error page rendering logic
return `<h1>Error ${type}</h1>`;
}Use conditional processing to handle different request types efficiently.
server.on('request', async (req, res) => {
// Skip authentication for public routes
if (req.url.pathname.startsWith('/public')) {
return true;
}
// Require authentication for protected routes
const user = await authenticate(req);
if (!user) {
res.setError('Unauthorized', {}, [], 401);
return false; // Abort processing
}
// Store user in request data
req.data.set('user', user);
return true;
});
async function authenticate(req: any) {
// Authentication logic
const token = req.headers.get('authorization');
if (token === 'Bearer valid-token') {
return { id: 1, username: 'user' };
}
return null;
}Monitor request performance to identify bottlenecks and optimize application performance.
server.on('request', async (req, res) => {
// Start timing
req.data.set('startTime', Date.now());
return true;
});
server.on('response', async (req, res) => {
// Calculate duration
const startTime = req.data.get('startTime');
const duration = Date.now() - startTime;
// Log slow requests
if (duration > 1000) {
console.warn(`Slow request: ${req.method} ${req.url.pathname} (${duration}ms)`);
}
return true;
});The following examples demonstrate common Route usage patterns and advanced lifecycle management techniques.
import Route from '@stackpress/ingest/Route';
import { server } from '@stackpress/ingest/http';
const app = server();
// Custom route with full lifecycle control
async function handleUserProfile(req: any, res: any, ctx: any) {
const userId = req.data.get('id');
try {
const user = await getUserById(userId);
const profile = await getUserProfile(userId);
res.setResults({
user,
profile,
timestamp: Date.now()
});
} catch (error) {
res.setError('Failed to load user profile', {}, [], 500);
}
}
// Use Route directly
const req = app.request({ url: '/users/123' });
const res = app.response();
req.data.set('id', '123');
const success = await Route.emit(handleUserProfile, req, res, app);
async function getUserById(id: string) {
// User lookup logic
return { id, name: 'John Doe', email: 'john@example.com' };
}
async function getUserProfile(id: string) {
// Profile lookup logic
return { bio: 'Software developer', location: 'San Francisco' };
}// Authentication middleware
server.on('request', async (req, res) => {
if (req.url.pathname.startsWith('/api/')) {
const token = req.headers.get('authorization');
if (!token) {
res.setError('Missing authorization header', {}, [], 401);
return false;
}
const user = await validateToken(token);
if (!user) {
res.setError('Invalid token', {}, [], 401);
return false;
}
req.data.set('user', user);
}
return true;
}, 10); // High priority
// Rate limiting middleware
server.on('request', async (req, res) => {
const clientIp = req.headers.get('x-forwarded-for') || 'unknown';
const allowed = await checkRateLimit(clientIp);
if (!allowed) {
res.setError('Rate limit exceeded', {}, [], 429);
return false;
}
return true;
}, 5); // Medium priority
// Logging middleware
server.on('request', async (req, res) => {
console.log(`${new Date().toISOString()} ${req.method} ${req.url.pathname}`);
return true;
}, 1); // Low priority
async function validateToken(token: string) {
// Token validation logic
return token === 'Bearer valid-token' ? { id: 1, username: 'user' } : null;
}
async function checkRateLimit(ip: string) {
// Rate limiting logic
return true; // Allow for demo
}// Global error handler
server.on('error', async (req, res) => {
// Log error details
const errorDetails = {
timestamp: new Date().toISOString(),
method: req.method,
url: req.url.href,
userAgent: req.headers.get('user-agent'),
error: res.error,
code: res.code,
stack: res.stack
};
console.error('Request error:', errorDetails);
// Send error to monitoring service
await sendToMonitoring(errorDetails);
// Customize error response based on type
if (res.code === 404) {
res.setHTML(await render404Page(req));
} else if (res.code === 500) {
res.setHTML(await render500Page());
} else if (res.code === 401) {
res.setJSON({
error: 'Authentication required',
loginUrl: '/auth/login'
});
}
return true;
});
async function sendToMonitoring(errorDetails: any) {
// Send to monitoring service
console.log('Sent to monitoring:', errorDetails);
}
async function render404Page(req: any) {
return `
<html>
<head><title>Page Not Found</title></head>
<body>
<h1>404 - Page Not Found</h1>
<p>The page ${req.url.pathname} could not be found.</p>
<a href="/">Go Home</a>
</body>
</html>
`;
}
async function render500Page() {
return `
<html>
<head><title>Server Error</title></head>
<body>
<h1>500 - Internal Server Error</h1>
<p>Something went wrong. Please try again later.</p>
</body>
</html>
`;
}