Skip to content

Request Context & Tracking

Learn how to track and manage requests in your Veloce-TS applications with automatic request IDs, timeouts, and cancellation support.

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
import { VeloceTS, createRequestContextMiddleware } from 'veloce-ts';
const app = new VeloceTS();
// Enable request context with logging
app.use(createRequestContextMiddleware({
timeout: 30000, // 30 second default timeout
logging: true, // Enable request logging
logHeaders: ['user-agent', 'x-forwarded-for']
}));
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);
}
}

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 header

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();
});

Request ID is automatically included in response headers:

HTTP/1.1 200 OK
X-Request-ID: abc-123-def-456
Content-Type: application/json

Provide your own request ID via header:

Terminal window
curl -H "X-Request-ID: custom-id-123" http://localhost:3000/api/data

The middleware will use the provided ID if available.

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 });
}
}
@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;
}
}
@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 };
}
@Get('/streaming')
async streamData(@AbortSignal() signal: AbortSignal) {
signal.addEventListener('abort', () => {
console.log('Request cancelled, cleaning up...');
// Cleanup resources
});
return await streamLargeDataset();
}

Set default timeout for all requests:

app.use(createRequestContextMiddleware({
timeout: 30000 // 30 seconds for all requests
}));

Override timeout for specific routes:

// Decorator API
@Get('/slow-operation')
@Timeout(60000) // 60 seconds
async slowOperation() {
return await verySlowProcess();
}
// Functional API
app.get('/slow', {
timeout: 60000,
handler: async () => {
return await verySlowProcess();
}
});

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;
}
}

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 completed
@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;
}
}

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)

Store custom data in request context:

import { setRequestMetadata, getRequestMetadata } from 'veloce-ts';
// In middleware
app.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' };
}
// Middleware 1: Extract user from token
app.use(async (c, next) => {
const token = c.req.header('authorization');
const user = await verifyToken(token);
setRequestMetadata(c, 'currentUser', user);
await next();
});
// Middleware 2: Check permissions
app.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}` };
}
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'
]
}));

If you only need request IDs without logging:

import { createSimpleRequestIdMiddleware } from 'veloce-ts';
app.use(createSimpleRequestIdMiddleware());

Enable it early in your middleware chain:

const app = new VeloceTS();
// First middleware
app.use(createRequestContextMiddleware({ logging: true }));
// Then other middleware
app.useCors();
app.useRateLimit({ max: 100 });
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;
}

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;
}
}

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;
}
import {
VeloceTS,
Controller,
Get,
Post,
Param,
Body,
RequestId,
AbortSignal,
Ctx,
createRequestContextMiddleware,
getLogger,
setRequestMetadata,
getRequestMetadata
} from 'veloce-ts';
const app = new VeloceTS();
// Enable request context
app.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);
  • Learn about Caching to reduce request processing time
  • Explore Logging for advanced logging strategies
  • Check Error Handling for proper error management