@struktos/adapter-express
v0.1.0
Published
Express.js adapter for Struktos - Enterprise-grade context propagation and request lifecycle management
Downloads
3
Maintainers
Readme
@struktos/adapter-express
Express.js adapter for Struktos - Enterprise-grade context propagation and request lifecycle management
🎯 What is this?
@struktos/adapter-express seamlessly integrates @struktos/core with Express.js applications, providing:
- ✅ Automatic Context Propagation - Go-style context flows through your entire request lifecycle
- ✅ Request Cancellation - Gracefully handle client disconnects and stop wasting resources
- ✅ TraceID Generation - Built-in distributed tracing support
- ✅ Zero Configuration - Works out of the box with sensible defaults
- ✅ Full TypeScript Support - Type-safe APIs with excellent IDE support
📦 Installation
npm install @struktos/core @struktos/adapter-express express🚀 Quick Start
import express from 'express';
import { createStruktosMiddleware, RequestContext } from '@struktos/adapter-express';
const app = express();
// Step 1: Add Struktos middleware
app.use(createStruktosMiddleware({
generateTraceId: () => crypto.randomUUID(),
extractUserId: (req) => req.user?.id
}));
// Step 2: Access context anywhere in your handlers
app.get('/api/users/:id', async (req, res) => {
const ctx = RequestContext.current();
const traceId = ctx?.get('traceId');
console.log(`Processing request ${traceId}`);
// Context automatically propagates through all async calls!
const user = await fetchUserFromDB(req.params.id);
res.json({ user, traceId });
});
app.listen(3000);That's it! Context now flows automatically through your entire application.
✨ Features
Automatic Context Propagation
Stop passing context objects through every function:
// ❌ Before: Manual context passing
async function processOrder(ctx, orderId) {
const user = await getUser(ctx, userId);
const items = await getItems(ctx, orderId);
// ...
}
// ✅ After: Automatic propagation
async function processOrder(orderId) {
const ctx = RequestContext.current();
const traceId = ctx?.get('traceId');
// Context flows automatically!
const user = await getUser(userId);
const items = await getItems(orderId);
// ...
}Request Cancellation
Handle client disconnects gracefully:
app.get('/api/long-task', async (req, res) => {
const ctx = RequestContext.current();
// Register cleanup handler
ctx?.onCancel(() => {
console.log('Client disconnected - cleaning up');
// Close DB connections, stop processing, etc.
});
// Check cancellation periodically in long-running tasks
for (let i = 0; i < 1000; i++) {
if (ctx?.isCancelled()) {
return res.status(499).json({ message: 'Request cancelled' });
}
await processChunk(i);
}
res.json({ status: 'completed' });
});TraceID & Request Tracking
Built-in support for distributed tracing:
app.use(createStruktosMiddleware({
generateTraceId: () => `trace-${Date.now()}-${randomBytes(4).toString('hex')}`,
onCancel: (req, traceId) => {
logger.info(`Request cancelled`, { traceId, url: req.url });
}
}));
app.get('/api/data', async (req, res) => {
const ctx = RequestContext.current();
const traceId = ctx?.get('traceId');
// Use traceId for logging, distributed tracing, etc.
logger.info('Fetching data', { traceId });
res.json({ traceId });
});📚 API Reference
createStruktosMiddleware(options)
Creates the main Struktos middleware.
Options
interface StruktosMiddlewareOptions {
// Function to generate trace ID (default: crypto.randomUUID())
generateTraceId?: () => string;
// Function to generate request ID (default: crypto.randomUUID())
generateRequestId?: () => string;
// Extract user ID from request
extractUserId?: (req: Request) => string | undefined;
// Add custom context data
additionalContext?: (req: Request) => Record<string, any>;
// Error handler for context initialization
onError?: (error: Error, req: Request, res: Response) => void;
// Callback when request is cancelled
onCancel?: (req: Request, traceId?: string) => void;
// Enable cancellation handling (default: true)
enableCancellation?: boolean;
// Custom context extractor
contextExtractor?: (req: Request) => Partial<StruktosContextData>;
}Example
app.use(createStruktosMiddleware({
generateTraceId: () => uuidv4(),
extractUserId: (req) => req.session?.userId,
additionalContext: (req) => ({
userAgent: req.headers['user-agent'],
ip: req.ip
}),
onCancel: (req, traceId) => {
metrics.increment('requests.cancelled', { traceId });
}
}));createContextLoggerMiddleware()
Middleware to log context information for debugging:
if (process.env.DEBUG === 'true') {
app.use(createContextLoggerMiddleware());
}createCancellationCheckMiddleware()
Middleware to check if request has been cancelled:
// Add before expensive operations
app.use('/api/expensive', createCancellationCheckMiddleware());Accessing Context
import { RequestContext } from '@struktos/adapter-express';
// Get current context
const ctx = RequestContext.current();
// Get values
const traceId = ctx?.get('traceId');
const userId = ctx?.get('userId');
// Set values
ctx?.set('customData', { foo: 'bar' });
// Check cancellation
if (ctx?.isCancelled()) {
// Handle cancellation
}
// Register cleanup
ctx?.onCancel(() => {
// Cleanup code
});StruktosRequest
Enhanced request object with context helpers:
import { StruktosRequest } from '@struktos/adapter-express';
app.get('/api/data', (req: StruktosRequest, res) => {
// Direct access to context metadata
console.log(req.traceId);
console.log(req.requestId);
console.log(req.requestTimestamp);
});🏗️ Architecture
HTTP Request
↓
Express App
↓
createStruktosMiddleware()
↓
RequestContext.run({ traceId, userId, ... })
↓
[Your Route Handlers]
↓
[Service Layer] ← Context automatically available
↓
[Data Layer] ← Context automatically available
↓
[External APIs] ← Context automatically available📊 Examples
Basic Usage
import express from 'express';
import { createStruktosMiddleware, RequestContext } from '@struktos/adapter-express';
const app = express();
app.use(createStruktosMiddleware());
app.get('/api/hello', (req, res) => {
const ctx = RequestContext.current();
res.json({
message: 'Hello!',
traceId: ctx?.get('traceId')
});
});
app.listen(3000);With Authentication
app.use(createStruktosMiddleware({
extractUserId: (req) => {
// Extract from JWT
const token = req.headers.authorization?.split(' ')[1];
if (token) {
const decoded = jwt.verify(token, SECRET);
return decoded.userId;
}
}
}));
app.get('/api/profile', async (req, res) => {
const ctx = RequestContext.current();
const userId = ctx?.get('userId');
if (!userId) {
return res.status(401).json({ error: 'Unauthorized' });
}
const profile = await getUserProfile(userId);
res.json(profile);
});Long-Running Tasks
app.get('/api/process-batch', async (req, res) => {
const ctx = RequestContext.current();
const items = await getItemsToProcess();
for (let i = 0; i < items.length; i++) {
// Check if client disconnected
if (ctx?.isCancelled()) {
return res.status(499).json({
message: 'Processing cancelled',
processed: i,
total: items.length
});
}
await processItem(items[i]);
}
res.json({ status: 'completed', total: items.length });
});Error Handling
app.use(createStruktosMiddleware({
onError: (error, req, res) => {
logger.error('Context initialization failed', { error, url: req.url });
res.status(500).json({
error: 'Internal Server Error',
message: 'Failed to initialize request context'
});
}
}));🔗 Integration with Logging
import winston from 'winston';
const logger = winston.createLogger({
format: winston.format.combine(
winston.format.timestamp(),
winston.format.json()
),
transports: [new winston.transports.Console()]
});
// Create logger wrapper that adds traceId
function log(level: string, message: string, meta?: any) {
const ctx = RequestContext.current();
const traceId = ctx?.get('traceId');
logger.log(level, message, { ...meta, traceId });
}
app.get('/api/data', async (req, res) => {
log('info', 'Fetching data');
const data = await fetchData();
log('info', 'Data fetched successfully', { count: data.length });
res.json(data);
});🧪 Testing
import request from 'supertest';
import { createStruktosMiddleware } from '@struktos/adapter-express';
describe('API Tests', () => {
let app;
beforeEach(() => {
app = express();
app.use(createStruktosMiddleware());
// ... setup routes
});
it('should propagate context', async () => {
const response = await request(app)
.get('/api/hello')
.expect(200);
expect(response.body).toHaveProperty('traceId');
});
});🤝 Related Packages
- @struktos/core - Core context propagation engine
- @struktos/adapter-fastify (coming soon) - Fastify adapter
- @struktos/auth (coming soon) - Authentication system
📄 License
MIT © Struktos.js Team
🔗 Links
Built with ❤️ for enterprise Node.js development
