npm package: @struktos/adapter-express
Description: Express.js adapter for Struktos - Enterprise-grade context propagation and request lifecycle management
Author: Struktos.js Team
Latest version: 0.1.0

# @struktos/adapter-express

> Express.js adapter for Struktos - Enterprise-grade context propagation and request lifecycle management

[![npm version](https://img.shields.io/npm/v/@struktos/adapter-express.svg)](https://www.npmjs.com/package/@struktos/adapter-express)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)

## 🎯 What is this?

`@struktos/adapter-express` seamlessly integrates [@struktos/core](https://www.npmjs.com/package/@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

```bash
npm install @struktos/core @struktos/adapter-express express
```

## 🚀 Quick Start

```typescript
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:

```typescript
// ❌ 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:

```typescript
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:

```typescript
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

```typescript
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

```typescript
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:

```typescript
if (process.env.DEBUG === 'true') {
  app.use(createContextLoggerMiddleware());
}
```

### createCancellationCheckMiddleware()

Middleware to check if request has been cancelled:

```typescript
// Add before expensive operations
app.use('/api/expensive', createCancellationCheckMiddleware());
```

### Accessing Context

```typescript
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:

```typescript
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

```typescript
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

```typescript
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

```typescript
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

```typescript
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

```typescript
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

```typescript
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](https://www.npmjs.com/package/@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

- [GitHub Repository](https://github.com/struktosjs/adapter-express)
- [Issue Tracker](https://github.com/struktosjs/adapter-express/issues)
- [NPM Package](https://www.npmjs.com/package/@struktos/adapter-express)
- [@struktos/core Documentation](https://www.npmjs.com/package/@struktos/core)

---

**Built with ❤️ for enterprise Node.js development**