Request Context & Tracking
Request Context & Tracking
Section titled “Request Context & Tracking”Learn how to track and manage requests in your Veloce-TS applications with automatic request IDs, timeouts, and cancellation support.
Table of Contents
Section titled “Table of Contents”- Overview
- Quick Start
- Request ID
- AbortSignal
- Timeouts
- Logging Integration
- Metadata Storage
- Best Practices
Overview
Section titled “Overview”Veloce-TS provides enhanced request context management that allows you to:
- Automatic Request IDs - Every request gets a unique UUID
- Request Tracking - Track requests through logs and headers
- Cancellation Support - Cancel long-running operations with AbortSignal
- Timeout Management - Configure request timeouts per route or globally
- Metadata Storage - Attach custom data to requests
- Logging Integration - Request ID propagates through all logs
Quick Start
Section titled “Quick Start”1. Enable Request Context Middleware
Section titled “1. Enable Request Context Middleware”import { VeloceTS, createRequestContextMiddleware } from 'veloce-ts';
const app = new VeloceTS();
// Enable request context with loggingapp.use(createRequestContextMiddleware({ timeout: 30000, // 30 second default timeout logging: true, // Enable request logging logHeaders: ['user-agent', 'x-forwarded-for']}));2. Use Request ID in Controllers
Section titled “2. Use Request ID in Controllers”import { Controller, Get, RequestId } from 'veloce-ts';
@Controller('/users')class UserController { @Get('/:id') async getUser( @Param('id') id: string, @RequestId() requestId: string ) { logger.info({ requestId }, 'Fetching user'); return await db.users.findById(id); }}Request ID
Section titled “Request ID”Automatic Generation
Section titled “Automatic Generation”Every request automatically receives a unique UUID:
// Request comes in// -> UUID generated: "abc-123-def-456"// -> Available in handlers via @RequestId()// -> Sent back in X-Request-ID headerAccessing Request ID
Section titled “Accessing Request ID”In Controllers:
@Controller('/api')class ApiController { @Get('/data') async getData(@RequestId() requestId: string) { console.log('Request ID:', requestId); return { data: 'example', requestId }; }}In Functional API:
import { getRequestId } from 'veloce-ts';
app.get('/data', { handler: async (c) => { const requestId = getRequestId(c); logger.info({ requestId }, 'Processing request'); return { data: 'example', requestId }; }});From Context:
import { getRequestId } from 'veloce-ts';
app.use(async (c, next) => { const requestId = getRequestId(c); console.log('Middleware - Request ID:', requestId); await next();});Response Headers
Section titled “Response Headers”Request ID is automatically included in response headers:
HTTP/1.1 200 OKX-Request-ID: abc-123-def-456Content-Type: application/jsonCustom Request ID
Section titled “Custom Request ID”Provide your own request ID via header:
curl -H "X-Request-ID: custom-id-123" http://localhost:3000/api/dataThe middleware will use the provided ID if available.
AbortSignal
Section titled “AbortSignal”Cancellable Operations
Section titled “Cancellable Operations”Use AbortSignal to cancel long-running operations:
@Controller('/tasks')class TaskController { @Get('/process') async processTask(@AbortSignal() signal: AbortSignal) { // Pass signal to long-running operations return await heavyComputation({ signal }); }
@Get('/download') async downloadFile(@AbortSignal() signal: AbortSignal) { // Cancel file download if client disconnects return await streamLargeFile({ signal }); }}With Fetch API
Section titled “With Fetch API”@Get('/external')async fetchExternal(@AbortSignal() signal: AbortSignal) { try { const response = await fetch('https://api.example.com/data', { signal // Pass signal to fetch }); return await response.json(); } catch (error) { if (error.name === 'AbortError') { return { error: 'Request was cancelled' }; } throw error; }}Manual Cancellation Check
Section titled “Manual Cancellation Check”@Get('/long-task')async longTask(@AbortSignal() signal: AbortSignal) { for (let i = 0; i < 100; i++) { // Check if cancelled if (signal.aborted) { return { cancelled: true, progress: i }; }
await processChunk(i); }
return { completed: true };}Listen for Cancellation
Section titled “Listen for Cancellation”@Get('/streaming')async streamData(@AbortSignal() signal: AbortSignal) { signal.addEventListener('abort', () => { console.log('Request cancelled, cleaning up...'); // Cleanup resources });
return await streamLargeDataset();}Timeouts
Section titled “Timeouts”Global Timeout
Section titled “Global Timeout”Set default timeout for all requests:
app.use(createRequestContextMiddleware({ timeout: 30000 // 30 seconds for all requests}));Route-Specific Timeout
Section titled “Route-Specific Timeout”Override timeout for specific routes:
// Decorator API@Get('/slow-operation')@Timeout(60000) // 60 secondsasync slowOperation() { return await verySlowProcess();}
// Functional APIapp.get('/slow', { timeout: 60000, handler: async () => { return await verySlowProcess(); }});Timeout Handling
Section titled “Timeout Handling”When a timeout occurs, the request is automatically aborted:
@Get('/task')async task(@AbortSignal() signal: AbortSignal) { try { const result = await longRunningTask({ signal }); return { result }; } catch (error) { if (signal.aborted) { // Timeout occurred return { error: 'Request timeout' }; } throw error; }}Logging Integration
Section titled “Logging Integration”Automatic Request ID in Logs
Section titled “Automatic Request ID in Logs”When using the request context middleware with logging enabled, request ID automatically appears in all logs:
import { getLogger } from 'veloce-ts';
@Controller('/api')class ApiController { private logger = getLogger().child({ component: 'ApiController' });
@Get('/data') async getData(@RequestId() requestId: string) { // All these logs will include the request ID this.logger.info('Starting data fetch'); this.logger.debug('Querying database'); this.logger.info('Data fetch completed');
return { data: 'example' }; }}Log output:
[2024-01-15 10:23:45] [requestId: abc-123] INFO: Starting data fetch[2024-01-15 10:23:45] [requestId: abc-123] DEBUG: Querying database[2024-01-15 10:23:45] [requestId: abc-123] INFO: Data fetch completedChild Logger with Request Context
Section titled “Child Logger with Request Context”@Get('/users/:id')async getUser( @Param('id') id: string, @RequestId() requestId: string) { const logger = getLogger().child({ requestId, userId: id, component: 'UserController' });
logger.info('Fetching user');
try { const user = await db.users.findById(id); logger.info('User fetched successfully'); return user; } catch (error) { logger.error('Failed to fetch user', error); throw error; }}Request Lifecycle Logging
Section titled “Request Lifecycle Logging”The middleware automatically logs request lifecycle:
app.use(createRequestContextMiddleware({ logging: true, // Enable lifecycle logging logHeaders: ['user-agent', 'referer']}));Output:
[requestId: abc-123] INFO: Request started GET /api/users/1[requestId: abc-123] INFO: Fetching user[requestId: abc-123] INFO: User fetched successfully[requestId: abc-123] INFO: Request completed 200 (duration: 45ms)Metadata Storage
Section titled “Metadata Storage”Attach Custom Data
Section titled “Attach Custom Data”Store custom data in request context:
import { setRequestMetadata, getRequestMetadata } from 'veloce-ts';
// In middlewareapp.use(async (c, next) => { setRequestMetadata(c, 'startTime', Date.now()); await next();
const duration = Date.now() - getRequestMetadata(c, 'startTime'); console.log(`Request took ${duration}ms`);});
// In handler@Get('/data')async getData(@Ctx() ctx: Context) { setRequestMetadata(ctx, 'cacheHit', true); return { data: 'example' };}Share Data Between Middleware
Section titled “Share Data Between Middleware”// Middleware 1: Extract user from tokenapp.use(async (c, next) => { const token = c.req.header('authorization'); const user = await verifyToken(token); setRequestMetadata(c, 'currentUser', user); await next();});
// Middleware 2: Check permissionsapp.use(async (c, next) => { const user = getRequestMetadata(c, 'currentUser'); if (!user.hasPermission('read')) { throw new ForbiddenException(); } await next();});
// Handler: Access user@Get('/protected')async protected(@Ctx() ctx: Context) { const user = getRequestMetadata(ctx, 'currentUser'); return { message: `Hello ${user.name}` };}Middleware Configuration
Section titled “Middleware Configuration”Full Configuration
Section titled “Full Configuration”import { createRequestContextMiddleware } from 'veloce-ts';
app.use(createRequestContextMiddleware({ // Default timeout for all requests timeout: 30000,
// Enable request lifecycle logging logging: true,
// Custom request ID generator requestIdGenerator: () => { return `req-${Date.now()}-${Math.random()}`; },
// Headers to include in logs logHeaders: [ 'user-agent', 'x-forwarded-for', 'referer' ]}));Simple Request ID Only
Section titled “Simple Request ID Only”If you only need request IDs without logging:
import { createSimpleRequestIdMiddleware } from 'veloce-ts';
app.use(createSimpleRequestIdMiddleware());Best Practices
Section titled “Best Practices”1. Always Use Request Context Middleware
Section titled “1. Always Use Request Context Middleware”Enable it early in your middleware chain:
const app = new VeloceTS();
// First middlewareapp.use(createRequestContextMiddleware({ logging: true }));
// Then other middlewareapp.useCors();app.useRateLimit({ max: 100 });2. Include Request ID in Error Responses
Section titled “2. Include Request ID in Error Responses”app.onError((error, c) => { const requestId = getRequestId(c);
return c.json({ error: error.message, requestId, timestamp: new Date().toISOString() }, error.status || 500);});3. Pass AbortSignal to All Async Operations
Section titled “3. Pass AbortSignal to All Async Operations”@Get('/data')async getData(@AbortSignal() signal: AbortSignal) { // Pass to database queries const users = await db.users.findMany({ signal });
// Pass to external APIs const external = await fetch('https://api.example.com', { signal });
// Pass to custom functions const processed = await processData(users, { signal });
return processed;}4. Use Request ID for Debugging
Section titled “4. Use Request ID for Debugging”When errors occur, include request ID:
@Get('/data')async getData(@RequestId() requestId: string) { try { return await fetchData(); } catch (error) { logger.error({ requestId, error }, 'Failed to fetch data'); throw error; }}5. Track Related Operations
Section titled “5. Track Related Operations”Use request metadata to track related operations:
@Post('/order')async createOrder( @Body() orderData: OrderDTO, @RequestId() requestId: string, @Ctx() ctx: Context) { const order = await db.orders.create(orderData);
// Store order ID for tracking setRequestMetadata(ctx, 'orderId', order.id);
// Send email (include request ID) await sendOrderConfirmation(order, { requestId });
// Log with context logger.info({ requestId, orderId: order.id }, 'Order created');
return order;}Complete Example
Section titled “Complete Example”import { VeloceTS, Controller, Get, Post, Param, Body, RequestId, AbortSignal, Ctx, createRequestContextMiddleware, getLogger, setRequestMetadata, getRequestMetadata} from 'veloce-ts';
const app = new VeloceTS();
// Enable request contextapp.use(createRequestContextMiddleware({ timeout: 30000, logging: true, logHeaders: ['user-agent']}));
@Controller('/api')class ApiController { private logger = getLogger().child({ component: 'ApiController' });
@Get('/users/:id') async getUser( @Param('id') id: string, @RequestId() requestId: string, @AbortSignal() signal: AbortSignal ) { this.logger.info({ requestId, userId: id }, 'Fetching user');
try { // Check for cancellation if (signal.aborted) { return { error: 'Request cancelled' }; }
const user = await db.users.findById(id, { signal });
this.logger.info({ requestId, userId: id }, 'User fetched');
return { user, requestId }; } catch (error) { this.logger.error({ requestId, userId: id, error }, 'Failed to fetch user'); throw error; } }
@Post('/process') async process( @Body() data: any, @RequestId() requestId: string, @AbortSignal() signal: AbortSignal, @Ctx() ctx: Context ) { this.logger.info({ requestId }, 'Starting process');
// Store start time setRequestMetadata(ctx, 'processStartTime', Date.now());
try { for (let i = 0; i < 10; i++) { // Check if cancelled if (signal.aborted) { this.logger.warn({ requestId, progress: i }, 'Process cancelled'); return { cancelled: true, progress: i }; }
await processChunk(i, { signal }); this.logger.debug({ requestId, progress: i + 1 }, 'Chunk processed'); }
const duration = Date.now() - getRequestMetadata(ctx, 'processStartTime'); this.logger.info({ requestId, duration }, 'Process completed');
return { completed: true, duration, requestId }; } catch (error) { this.logger.error({ requestId, error }, 'Process failed'); throw error; } }}
app.include(ApiController);app.listen(3000);Next Steps
Section titled “Next Steps”- Learn about Caching to reduce request processing time
- Explore Logging for advanced logging strategies
- Check Error Handling for proper error management